├── Section6 ├── data.db ├── __init__.py ├── modules │ ├── __init__.py │ ├── user.py │ └── item.py ├── resources │ ├── __init__.py │ ├── user.py │ └── item.py ├── settings.py ├── db.py ├── requirements.txt ├── security.py ├── test.py └── app.py ├── requirements.txt ├── Section4 ├── settings.py ├── user.py ├── requirements.txt ├── security.py └── app.py ├── Section5 ├── settings.py ├── data.db ├── requirements.txt ├── security.py ├── create_tables.py ├── app.py ├── test.py ├── user.py └── item.py ├── pyproject.toml ├── Section3 ├── requirements.txt ├── templates │ └── index.html └── app.py ├── setup.cfg ├── .pre-commit-config.yaml ├── README.md └── .gitignore /Section6/data.db: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Section6/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Section6/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Section6/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit 2 | -------------------------------------------------------------------------------- /Section4/settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | -------------------------------------------------------------------------------- /Section5/settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | -------------------------------------------------------------------------------- /Section6/settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 79 3 | -------------------------------------------------------------------------------- /Section6/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /Section5/data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaviRP2018/courses_rest_apis_with_flask_and_python/HEAD/Section5/data.db -------------------------------------------------------------------------------- /Section3/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | itsdangerous==1.1.0 4 | Jinja2==2.11.2 5 | MarkupSafe==1.1.1 6 | Werkzeug==1.0.1 7 | -------------------------------------------------------------------------------- /Section4/user.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, _id, username, password): 3 | self.id = _id 4 | self.username = username 5 | self.password = password 6 | -------------------------------------------------------------------------------- /Section4/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==8.1.1 2 | click==7.1.2 3 | Flask==1.1.2 4 | Flask-JWT==0.3.2 5 | Flask-RESTful==0.3.8 6 | itsdangerous==1.1.0 7 | Jinja2==2.11.3 8 | MarkupSafe==1.1.1 9 | PyJWT==1.4.2 10 | pytz==2021.1 11 | six==1.15.0 12 | Werkzeug==1.0.1 13 | -------------------------------------------------------------------------------- /Section5/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==8.1.1 2 | click==7.1.2 3 | Flask==1.1.2 4 | Flask-JWT==0.3.2 5 | Flask-RESTful==0.3.8 6 | itsdangerous==1.1.0 7 | Jinja2==2.11.3 8 | MarkupSafe==1.1.1 9 | PyJWT==1.4.2 10 | pytz==2021.1 11 | six==1.15.0 12 | Werkzeug==1.0.1 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | max-complexity = 15 4 | 5 | [isort] 6 | atomic=true 7 | line_length=99 8 | force_grid_wrap=0 9 | include_trailing_comma=true 10 | lines_after_imports=2 11 | lines_between_types=1 12 | multi_line_output=3 13 | not_skip=__init__.py 14 | use_parentheses=true -------------------------------------------------------------------------------- /Section6/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==8.1.1 2 | click==7.1.2 3 | Flask==1.1.2 4 | Flask-JWT==0.3.2 5 | Flask-RESTful==0.3.8 6 | Flask-SQLAlchemy==2.4.4 7 | itsdangerous==1.1.0 8 | Jinja2==2.11.3 9 | MarkupSafe==1.1.1 10 | PyJWT==1.4.2 11 | pytz==2021.1 12 | six==1.15.0 13 | SQLAlchemy==1.3.23 14 | Werkzeug==1.0.1 15 | -------------------------------------------------------------------------------- /Section5/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | 3 | from Section5.user import User 4 | 5 | 6 | def authenticate(username, password): 7 | user = User.find_by_username(username) 8 | if user and safe_str_cmp(user.password, password): 9 | return user 10 | 11 | 12 | def identity(payload): 13 | user_id = payload["identity"] 14 | return User.find_by_id(user_id) 15 | -------------------------------------------------------------------------------- /Section6/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | 3 | from Section6.modules.user import UserModel 4 | 5 | 6 | def authenticate(username, password): 7 | user = UserModel.find_by_username(username) 8 | if user and safe_str_cmp(user.password, password): 9 | return user 10 | 11 | 12 | def identity(payload): 13 | user_id = payload["identity"] 14 | return UserModel.find_by_id(user_id) 15 | -------------------------------------------------------------------------------- /Section5/create_tables.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | connection = sqlite3.connect("data.db") 4 | cursor = connection.cursor() 5 | 6 | cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username text, password text);") 7 | 8 | cursor.execute("CREATE TABLE IF NOT EXISTS items (name text, price real);") 9 | 10 | cursor.execute("INSERT INTO items VALUES ('test', 10.99);") 11 | 12 | connection.commit() 13 | connection.close() 14 | -------------------------------------------------------------------------------- /Section4/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | 3 | from Section4.user import User 4 | 5 | users = [User(1, "bob", "asdf")] 6 | 7 | username_mapping = {u.username: u for u in users} 8 | userid_mapping = {u.id: u for u in users} 9 | 10 | 11 | def authenticate(username, password): 12 | user = username_mapping.get(username) 13 | if user and safe_str_cmp(user.password, password): 14 | return user 15 | 16 | 17 | def identity(payload): 18 | user_id = payload["identity"] 19 | return userid_mapping.get(user_id) 20 | -------------------------------------------------------------------------------- /Section5/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_jwt import JWT 3 | from flask_restful import Api 4 | 5 | from Section5.item import Item, ItemList 6 | from Section5.security import authenticate, identity 7 | from Section5.settings import DEBUG 8 | from Section5.user import UserRegister 9 | 10 | app = Flask(__name__) 11 | app.secret_key = "Davi" 12 | api = Api(app) 13 | 14 | jwt = JWT(app, authenticate, identity) # /auth 15 | 16 | api.add_resource(Item, "/item/") 17 | api.add_resource(ItemList, "/items") 18 | api.add_resource(UserRegister, "/register") 19 | 20 | if __name__ == "__main__": 21 | app.run(port=5000, debug=DEBUG) 22 | -------------------------------------------------------------------------------- /Section5/test.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | connection = sqlite3.connect("data.db") 4 | cursor = connection.cursor() 5 | 6 | create_table_query = "CREATE TABLE users (id int, username text, password text);" 7 | cursor.execute(create_table_query) 8 | 9 | user = (1, "jose", "asdf") 10 | insert_query = "INSERT INTO users VALUES (?, ?, ?);" 11 | cursor.execute(insert_query, user) 12 | 13 | users = [ 14 | (2, "rolf", "asdf"), 15 | (3, "anne", "xyz"), 16 | ] 17 | cursor.executemany(insert_query, users) 18 | 19 | select_query = "SELECT * FROM users;" 20 | for row in cursor.execute(select_query): 21 | print(row) 22 | 23 | connection.commit() 24 | connection.close() 25 | -------------------------------------------------------------------------------- /Section6/test.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | connection = sqlite3.connect("data.db") 4 | cursor = connection.cursor() 5 | 6 | create_table_query = "CREATE TABLE users (id int, username text, password text);" 7 | cursor.execute(create_table_query) 8 | 9 | user = (1, "jose", "asdf") 10 | insert_query = "INSERT INTO users VALUES (?, ?, ?);" 11 | cursor.execute(insert_query, user) 12 | 13 | users = [ 14 | (2, "rolf", "asdf"), 15 | (3, "anne", "xyz"), 16 | ] 17 | cursor.executemany(insert_query, users) 18 | 19 | select_query = "SELECT * FROM users;" 20 | for row in cursor.execute(select_query): 21 | print(row) 22 | 23 | connection.commit() 24 | connection.close() 25 | -------------------------------------------------------------------------------- /Section6/modules/user.py: -------------------------------------------------------------------------------- 1 | from Section6.db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = "users" 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(10)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def save_to_db(self): 16 | db.session.add(self) 17 | db.session.commit() 18 | 19 | @classmethod 20 | def find_by_username(cls, username): 21 | return cls.query.filter_by(username=username).first() 22 | 23 | @classmethod 24 | def find_by_id(cls, pk): 25 | return cls.query.filter_by(id=pk).first() 26 | -------------------------------------------------------------------------------- /Section3/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | My webserver 19 | 20 | 21 | 22 |
23 | lixo 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Section6/modules/item.py: -------------------------------------------------------------------------------- 1 | from Section6.db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = "items" 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | def __init__(self, name, price): 12 | self.name = name 13 | self.price = price 14 | 15 | def json(self): 16 | return {"name": self.name, "price": self.price} 17 | 18 | @classmethod 19 | def find_by_name(cls, name): 20 | return cls.query.filter_by(name=name).first() # ~= SELECT * FROM items WHERE name=name LIMIT 1; 21 | 22 | def save_to_db(self): 23 | db.session.add(self) 24 | db.session.commit() 25 | 26 | def delete_from_db(self): 27 | db.session.delete(self) 28 | db.session.commit() 29 | -------------------------------------------------------------------------------- /Section6/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | 3 | from Section6.modules.user import UserModel 4 | 5 | 6 | class UserRegister(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument( 9 | "username", 10 | type=str, 11 | required=True, 12 | help="This field cannot be left blank!", 13 | ) 14 | parser.add_argument( 15 | "password", 16 | type=str, 17 | required=True, 18 | help="This field cannot be left blank!", 19 | ) 20 | 21 | def post(self): 22 | data = UserRegister.parser.parse_args() 23 | 24 | if UserModel.find_by_username(data["username"]): 25 | return {"message": "A user with that username already exists."}, 400 26 | 27 | user = UserModel(**data) 28 | user.save_to_db() 29 | 30 | return {"message": "User created successfully."}, 201 31 | -------------------------------------------------------------------------------- /Section6/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_jwt import JWT 3 | from flask_restful import Api 4 | 5 | from Section6.resources.item import Item, ItemList 6 | from Section6.resources.user import UserRegister 7 | from Section6.security import authenticate, identity 8 | from Section6.settings import DEBUG 9 | from db import db 10 | 11 | app = Flask(__name__) 12 | app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db" 13 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 14 | app.secret_key = "Davi" 15 | api = Api(app) 16 | db.init_app(app) 17 | 18 | 19 | @app.before_first_request 20 | def create_tables(): 21 | db.create_all() 22 | 23 | 24 | jwt = JWT(app, authenticate, identity) # /auth 25 | 26 | api.add_resource(Item, "/item/") 27 | api.add_resource(ItemList, "/items") 28 | api.add_resource(UserRegister, "/register") 29 | 30 | if __name__ == "__main__": 31 | app.run(port=5000, debug=DEBUG) 32 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3 3 | repos: 4 | - repo: https://github.com/jorisroovers/gitlint.git 5 | rev: v0.19.1 6 | hooks: 7 | - id: gitlint 8 | 9 | - repo: https://github.com/ambv/black.git 10 | rev: 24.1.1 11 | hooks: 12 | - id: black 13 | language_version: python3 14 | additional_dependencies: ['click==8.0.4'] 15 | 16 | - repo: https://github.com/pycqa/flake8.git 17 | rev: 7.0.0 18 | hooks: 19 | - id: flake8 20 | additional_dependencies: [flake8-bugbear] 21 | 22 | - repo: https://github.com/pycqa/isort.git 23 | rev: 5.13.2 24 | hooks: 25 | - id: isort 26 | args: ["--profile", "black"] 27 | 28 | - repo: https://github.com/pre-commit/pre-commit-hooks.git 29 | rev: v4.5.0 30 | hooks: 31 | - id: trailing-whitespace 32 | - id: end-of-file-fixer 33 | - id: debug-statements 34 | -------------------------------------------------------------------------------- /Section3/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request, render_template 2 | 3 | app = Flask(__name__) 4 | 5 | stores = [ 6 | { 7 | "name": "My Wonderful Store", 8 | "items": [ 9 | { 10 | "name": "My item", 11 | "price": 15.99, 12 | } 13 | ] 14 | } 15 | ] 16 | 17 | 18 | @app.route("/") 19 | def home(): 20 | return render_template("index.html") 21 | 22 | 23 | # POST - used to receive data 24 | # GET - used to end data back only 25 | 26 | # POST /store data: {name:} 27 | @app.route("/store", methods=["POST"]) 28 | def create_store(): 29 | request_data = request.get_json() 30 | new_store = { 31 | "name": request_data["name"], 32 | "items": [], 33 | } 34 | stores.append(new_store) 35 | return jsonify(new_store) 36 | 37 | 38 | # GET /store/ 39 | @app.route("/store/") 40 | def get_store(name): 41 | # Iterate over stores 42 | # if the store name matches, return it 43 | # if none match, return an error message 44 | for store in stores: 45 | if store["name"] == name: 46 | return jsonify(store) 47 | return jsonify({"message": "store not found"}) 48 | 49 | 50 | # GET / store 51 | @app.route("/store") 52 | def get_stores(): 53 | return jsonify({"stores": stores}) 54 | 55 | 56 | # POST /store//item {name:, price:} 57 | @app.route("/store//item", methods=["POST"]) 58 | def create_item_in_store(name): 59 | request_data = request.get_json() 60 | for store in stores: 61 | if store["name"] == name: 62 | new_item = { 63 | "name": request_data["name"], 64 | "price": request_data["price"] 65 | } 66 | store["items"].append(new_item) 67 | return jsonify(new_item) 68 | return jsonify({"message": "store not found"}) 69 | 70 | 71 | # GET /store//item 72 | @app.route("/store//item") 73 | def get_items_in_store(name): 74 | for store in stores: 75 | if store["name"] == name: 76 | return jsonify({"items": store["items"]}) 77 | return jsonify({"message": "store not found"}) 78 | 79 | 80 | if __name__ == "__main__": 81 | app.run(port=5000) 82 | -------------------------------------------------------------------------------- /Section5/user.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from flask_restful import Resource, reqparse 4 | 5 | 6 | class User: 7 | def __init__(self, _id, username, password): 8 | self.id = _id 9 | self.username = username 10 | self.password = password 11 | 12 | @classmethod 13 | def find_by_username(cls, username): 14 | connection = sqlite3.connect("data.db") 15 | cursor = connection.cursor() 16 | 17 | query = "SELECT * FROM users WHERE username=?;" 18 | result = cursor.execute(query, (username,)) 19 | row = result.fetchone() 20 | 21 | user = cls(*row) if row else None 22 | 23 | connection.close() 24 | return user 25 | 26 | @classmethod 27 | def find_by_id(cls, _id): 28 | connection = sqlite3.connect("data.db") 29 | cursor = connection.cursor() 30 | 31 | query = "SELECT * FROM users WHERE id=?;" 32 | result = cursor.execute(query, (_id,)) 33 | row = result.fetchone() 34 | 35 | user = cls(*row) if row else None 36 | 37 | connection.close() 38 | return user 39 | 40 | 41 | class UserRegister(Resource): 42 | parser = reqparse.RequestParser() 43 | parser.add_argument( 44 | "username", 45 | type=str, 46 | required=True, 47 | help="This field cannot be left blank!", 48 | ) 49 | parser.add_argument( 50 | "password", 51 | type=str, 52 | required=True, 53 | help="This field cannot be left blank!", 54 | ) 55 | 56 | def post(self): 57 | data = UserRegister.parser.parse_args() 58 | 59 | if User.find_by_username(data["username"]): 60 | return {"message": "A user with that username already exists."}, 400 61 | 62 | connection = sqlite3.connect("data.db") 63 | cursor = connection.cursor() 64 | 65 | # query = "SELECT id FROM users WHERE username=?;" 66 | # result = cursor.execute(query, (data["username"],)) 67 | # row = result.fetchone() 68 | # if not row: 69 | query = "INSERT INTO users VALUES (NULL, ?, ?);" 70 | cursor.execute(query, (data["username"], data["password"])) 71 | 72 | connection.commit() 73 | connection.close() 74 | 75 | return {"message": "User created successfully."}, 201 76 | -------------------------------------------------------------------------------- /Section4/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | from flask_jwt import JWT 3 | from flask_restful import Resource, Api, reqparse 4 | 5 | from Section4.security import authenticate, identity 6 | from Section4.settings import DEBUG 7 | 8 | app = Flask(__name__) 9 | app.secret_key = "Davi" 10 | api = Api(app) 11 | 12 | jwt = JWT(app, authenticate, identity) # /auth 13 | 14 | items = [] 15 | 16 | 17 | class Item(Resource): 18 | parser = reqparse.RequestParser() 19 | parser.add_argument( 20 | "price", 21 | type=float, 22 | required=True, 23 | help="This field cannot be left blank!", 24 | ) 25 | 26 | # @jwt_required() 27 | def get(self, name): 28 | item = next(filter(lambda x: x["name"] == name, items), None) 29 | return {"item": item}, 200 if item is not None else 404 30 | 31 | # @jwt_required() 32 | def post(self, name): 33 | if next(filter(lambda x: x["name"] == name, items), None) is not None: 34 | return {"message": f"An item with name '{name}' already exists."}, 400 35 | # data = request.get_json(force=True) # force: ignore header Content-Type and try to format to json 36 | # data = request.get_json(silent=True) # silent: ignore header Content-Type and return null 37 | data = Item.parser.parse_args() 38 | item = {"name": name, "price": data["price"]} 39 | items.append(item) 40 | return item, 201 41 | 42 | # @jwt_required() 43 | def delete(self, name): 44 | global items 45 | # python tries to use the new 'items' variable 46 | items = list(filter(lambda x: x["name"] != name, items)) 47 | return {"message": "item deleted"} 48 | 49 | # @jwt_required() 50 | def put(self, name): 51 | data = Item.parser.parse_args() 52 | 53 | item = next(filter(lambda x: x["name"] == name, items), None) 54 | if item is None: 55 | item = {"name": name, "price": data["price"]} 56 | items.append(item) 57 | else: 58 | item.update(data) 59 | return {"item": item} 60 | 61 | 62 | class ItemList(Resource): 63 | # @jwt_required() 64 | def get(self): 65 | return {"items": items} 66 | 67 | 68 | api.add_resource(Item, "/item/") 69 | api.add_resource(ItemList, "/items") 70 | 71 | if __name__ == "main": 72 | app.run(port=5000, debug=DEBUG) 73 | -------------------------------------------------------------------------------- /Section6/resources/item.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from flask_restful import Resource, reqparse 4 | 5 | from Section6.modules.item import ItemModel 6 | 7 | 8 | class Item(Resource): 9 | parser = reqparse.RequestParser() 10 | parser.add_argument( 11 | "price", 12 | type=float, 13 | required=True, 14 | help="This field cannot be left blank!", 15 | ) 16 | 17 | # @jwt_required() 18 | def get(self, name): 19 | try: 20 | item = ItemModel.find_by_name(name) 21 | except Exception as err: 22 | return {"message": f"An error occurred searching the item.\nError: {err}"}, 500 23 | else: 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | # @jwt_required() 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return {"message": f"An item with name '{name}' already exists."}, 400 32 | 33 | data = Item.parser.parse_args() 34 | item = ItemModel(name, data["price"]) 35 | 36 | try: 37 | item.save_to_db() 38 | except Exception as err: 39 | return {"message": f"An error occurred inserting the item.\nError: {err}"}, 500 40 | 41 | return item.json(), 201 42 | 43 | # @jwt_required() 44 | def delete(self, name): 45 | item = ItemModel.find_by_name(name) 46 | if not item: 47 | return {"message": "Item already deleted."} 48 | item.delete_from_db() 49 | return {"message": "item deleted"} 50 | 51 | # @jwt_required() 52 | def put(self, name): 53 | data = Item.parser.parse_args() 54 | 55 | try: 56 | item = ItemModel.find_by_name(name) 57 | except Exception as err: 58 | return {"message": f"An error occurred searching the item.\nError: {err}"}, 500 59 | else: 60 | if item is None: 61 | try: 62 | item = ItemModel(name, data["price"]) 63 | item.save_to_db() 64 | except Exception as err: 65 | return {"message": f"An error occurred inserting the item.\nError: {err}"}, 500 66 | else: 67 | try: 68 | item.price = data["price"] 69 | except Exception as err: 70 | return {"message": f"An error occurred updating the item.\nError: {err}"}, 500 71 | 72 | return item.json() 73 | 74 | 75 | class ItemList(Resource): 76 | # @jwt_required() 77 | def get(self): 78 | return {"items": [item.json() for item in ItemModel.query.all()]} 79 | # return {"items": list(map(lambda item: item.json(), ItemModel.query.all()))} 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # courses-rest-apis-with-flask-and-python 2 | https://www.udemy.com/course/rest-api-flask-and-python/learn/lecture/5960108#overview 3 | 4 | Are you tired of boring, outdated, incomplete, or incorrect tutorials? I say no more to copy-pasting code that you don’t understand. 5 | 6 | Welcome to the bestselling REST API course on Udemy! I'm Jose. I'm a software engineer, here to help you truly understand and develop your skills in web and REST API development with Python and Flask. 7 | 8 | Production-ready REST APIs with Flask 9 | 10 | This course will guide you in creating simple, intermediate, and advanced REST APIs including authentication, deployments, databases, and much more. 11 | 12 | We'll start with a Python refresher that will take you from the very basics to some of the most advanced features of Python—that's all the Python you need to complete the course. 13 | 14 | Using Flask and popular extensions Flask-RESTful, Flask-JWT, and Flask-SQLAlchemy we will dive right into developing complete, solid, production-ready REST APIs. 15 | 16 | We will also look into essential technologies Git, Heroku, and nginx. 17 | 18 | You'll be able to... 19 | 20 | Create resource-based, production-ready REST APIs using Python, Flask, and popular Flask extensions; 21 | 22 | Handle secure user registration and authentication with Flask. 23 | 24 | Using SQLAlchemy and Flask-SQLAlchemy to easily and efficiently store resources to a database; and 25 | 26 | Understand the complex intricacies of deployments and the performance of Flask REST APIs. 27 | 28 | 29 | 30 | But what is a REST API anyway? 31 | 32 | A REST API is an application that accepts data from clients and returns data back. For example, a REST API could accept text data from the client, such as a username and password, and return whether that is a valid user in the database. 33 | 34 | When developing REST APIs, our clients are usually web apps or mobile apps. That's in contrast to when we make websites, where the clients are usually the users themselves. 35 | 36 | Together we'll develop a REST API that not only allows clients to authenticate but also to store and retrieve any data you want from a database. Learning this will help you develop any REST API that you need for your own projects! 37 | 38 | 39 | 40 | I pride myself on providing excellent support and feedback to every single student. I am always available to guide you and answer your questions. 41 | 42 | I'll see you on the inside. Take your first step towards REST API mastery! 43 | 44 | O que você aprenderá 45 | Connect web or mobile applications to databases and servers via REST APIs 46 | Create secure and reliable REST APIs which include authentication, logging, caching, and more 47 | Understand the different layers of a web server and how web applications interact with each other 48 | Handle seamless user authentication with advanced features like token refresh 49 | Handle log-outs and prevent abuse in your REST APIs with JWT blacklisting 50 | Develop professional-grade REST APIs with expert instruction 51 | Há algum requisito ou pré-requisito para o curso? 52 | Some prior programming experience in any programming language will help. The course includes a full Python refresher course. 53 | All software used in the course is provided, and completely free 54 | Complete beginners may wish to take a beginner Python course first, and then transition to this course afterwards 55 | Para quem é este curso: 56 | Students wanting to extend the capabilities of mobile and web applications by using server-side technologies 57 | Software developers looking to expand their skill-set by learning to develop professional grade REST APIs 58 | Those looking to learn Python while specifically catering to web services 59 | -------------------------------------------------------------------------------- /Section5/item.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from flask_restful import Resource, reqparse 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument( 9 | "price", 10 | type=float, 11 | required=True, 12 | help="This field cannot be left blank!", 13 | ) 14 | 15 | @classmethod 16 | def find_by_name(cls, name): 17 | connection = sqlite3.connect("data.db") 18 | cursor = connection.cursor() 19 | 20 | query = "SELECT * FROM items WHERE name=?;" 21 | result = cursor.execute(query, (name,)) 22 | row = result.fetchone() 23 | connection.close() 24 | 25 | if row: 26 | return {"item": {"name": row[0], "price": row[1]}} 27 | 28 | @classmethod 29 | def insert(cls, item): 30 | connection = sqlite3.connect("data.db") 31 | cursor = connection.cursor() 32 | 33 | query = "INSERT INTO items VALUES (?, ?);" 34 | cursor.execute(query, (item["name"], item["price"])) 35 | 36 | connection.commit() 37 | connection.close() 38 | 39 | @classmethod 40 | def update(cls, item): 41 | connection = sqlite3.connect("data.db") 42 | cursor = connection.cursor() 43 | 44 | query = "UPDATE items SET price=? WHERE name=?;" 45 | cursor.execute(query, (item["price"], item["name"])) 46 | 47 | connection.commit() 48 | connection.close() 49 | 50 | # @jwt_required() 51 | def get(self, name): 52 | try: 53 | item = self.find_by_name(name) 54 | except Exception as err: 55 | return {"message": f"An error occurred searching the item.\nError: {err}"}, 500 56 | else: 57 | if item: 58 | return item 59 | return {"message": "Item not found"}, 404 60 | 61 | # @jwt_required() 62 | def post(self, name): 63 | if self.find_by_name(name): 64 | return {"message": f"An item with name '{name}' already exists."}, 400 65 | 66 | data = Item.parser.parse_args() 67 | item = {"name": name, "price": data["price"]} 68 | 69 | try: 70 | self.insert(item) 71 | except Exception as err: 72 | return {"message": f"An error occurred inserting the item.\nError: {err}"}, 500 73 | 74 | return item, 201 75 | 76 | # @jwt_required() 77 | def delete(self, name): 78 | if not self.find_by_name(name): 79 | return {"message": "Item already deleted."} 80 | 81 | connection = sqlite3.connect("data.db") 82 | cursor = connection.cursor() 83 | 84 | query = "DELETE FROM items WHERE name=?;" 85 | cursor.execute(query, (name,)) 86 | 87 | connection.commit() 88 | connection.close() 89 | 90 | return {"message": "item deleted"} 91 | 92 | # @jwt_required() 93 | def put(self, name): 94 | data = Item.parser.parse_args() 95 | 96 | try: 97 | item = self.find_by_name(name) 98 | except Exception as err: 99 | return {"message": f"An error occurred searching the item.\nError: {err}"}, 500 100 | else: 101 | updated_item = {"name": name, "price": data["price"]} 102 | 103 | if item is None: 104 | try: 105 | self.insert(updated_item) 106 | except Exception as err: 107 | return {"message": f"An error occurred inserting the item.\nError: {err}"}, 500 108 | else: 109 | try: 110 | self.update(updated_item) 111 | except Exception as err: 112 | return {"message": f"An error occurred updating the item.\nError: {err}"}, 500 113 | 114 | return {"item": updated_item} 115 | 116 | 117 | class ItemList(Resource): 118 | # @jwt_required() 119 | def get(self): 120 | connection = sqlite3.connect("data.db") 121 | cursor = connection.cursor() 122 | 123 | query = "SELECT * FROM items;" 124 | result = cursor.execute(query) 125 | items = [{"name": row[0], "price": row[1]} for row in result] 126 | 127 | connection.close() 128 | 129 | return {"items": items} 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,pycharm+all,pycharm+iml,vscode,macos,flask 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,pycharm+all,pycharm+iml,vscode,macos,flask 4 | 5 | ### Flask ### 6 | instance/* 7 | !instance/.gitignore 8 | .webassets-cache 9 | 10 | ### Flask.Python Stack ### 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | pip-wheel-metadata/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | pytestdebug.log 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | db.sqlite3 73 | db.sqlite3-journal 74 | 75 | # Flask stuff: 76 | instance/ 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | doc/_build/ 84 | 85 | # PyBuilder 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 106 | __pypackages__/ 107 | 108 | # Celery stuff 109 | celerybeat-schedule 110 | celerybeat.pid 111 | 112 | # SageMath parsed files 113 | *.sage.py 114 | 115 | # Environments 116 | .env 117 | .venv 118 | env/ 119 | venv/ 120 | ENV/ 121 | env.bak/ 122 | venv.bak/ 123 | pythonenv* 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # pytype static type analyzer 144 | .pytype/ 145 | 146 | # profiling data 147 | .prof 148 | 149 | ### macOS ### 150 | # General 151 | .DS_Store 152 | .AppleDouble 153 | .LSOverride 154 | 155 | # Icon must end with two \r 156 | Icon 157 | 158 | 159 | # Thumbnails 160 | ._* 161 | 162 | # Files that might appear in the root of a volume 163 | .DocumentRevisions-V100 164 | .fseventsd 165 | .Spotlight-V100 166 | .TemporaryItems 167 | .Trashes 168 | .VolumeIcon.icns 169 | .com.apple.timemachine.donotpresent 170 | 171 | # Directories potentially created on remote AFP share 172 | .AppleDB 173 | .AppleDesktop 174 | Network Trash Folder 175 | Temporary Items 176 | .apdisk 177 | 178 | ### PyCharm ### 179 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 180 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 181 | 182 | # User-specific stuff 183 | .idea/**/workspace.xml 184 | .idea/**/tasks.xml 185 | .idea/**/usage.statistics.xml 186 | .idea/**/dictionaries 187 | .idea/**/shelf 188 | 189 | # Generated files 190 | .idea/**/contentModel.xml 191 | 192 | # Sensitive or high-churn files 193 | .idea/**/dataSources/ 194 | .idea/**/dataSources.ids 195 | .idea/**/dataSources.local.xml 196 | .idea/**/sqlDataSources.xml 197 | .idea/**/dynamic.xml 198 | .idea/**/uiDesigner.xml 199 | .idea/**/dbnavigator.xml 200 | 201 | # Gradle 202 | .idea/**/gradle.xml 203 | .idea/**/libraries 204 | 205 | # Gradle and Maven with auto-import 206 | # When using Gradle or Maven with auto-import, you should exclude module files, 207 | # since they will be recreated, and may cause churn. Uncomment if using 208 | # auto-import. 209 | # .idea/artifacts 210 | # .idea/compiler.xml 211 | # .idea/jarRepositories.xml 212 | # .idea/modules.xml 213 | # .idea/*.iml 214 | # .idea/modules 215 | # *.iml 216 | # *.ipr 217 | 218 | # CMake 219 | cmake-build-*/ 220 | 221 | # Mongo Explorer plugin 222 | .idea/**/mongoSettings.xml 223 | 224 | # File-based project format 225 | *.iws 226 | 227 | # IntelliJ 228 | out/ 229 | 230 | # mpeltonen/sbt-idea plugin 231 | .idea_modules/ 232 | 233 | # JIRA plugin 234 | atlassian-ide-plugin.xml 235 | 236 | # Cursive Clojure plugin 237 | .idea/replstate.xml 238 | 239 | # Crashlytics plugin (for Android Studio and IntelliJ) 240 | com_crashlytics_export_strings.xml 241 | crashlytics.properties 242 | crashlytics-build.properties 243 | fabric.properties 244 | 245 | # Editor-based Rest Client 246 | .idea/httpRequests 247 | 248 | # Android studio 3.1+ serialized cache file 249 | .idea/caches/build_file_checksums.ser 250 | 251 | ### PyCharm Patch ### 252 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 253 | 254 | # *.iml 255 | # modules.xml 256 | # .idea/misc.xml 257 | # *.ipr 258 | 259 | # Sonarlint plugin 260 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 261 | .idea/**/sonarlint/ 262 | 263 | # SonarQube Plugin 264 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 265 | .idea/**/sonarIssues.xml 266 | 267 | # Markdown Navigator plugin 268 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 269 | .idea/**/markdown-navigator.xml 270 | .idea/**/markdown-navigator-enh.xml 271 | .idea/**/markdown-navigator/ 272 | 273 | # Cache file creation bug 274 | # See https://youtrack.jetbrains.com/issue/JBR-2257 275 | .idea/$CACHE_FILE$ 276 | 277 | # CodeStream plugin 278 | # https://plugins.jetbrains.com/plugin/12206-codestream 279 | .idea/codestream.xml 280 | 281 | ### PyCharm+all ### 282 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 283 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 284 | 285 | # User-specific stuff 286 | 287 | # Generated files 288 | 289 | # Sensitive or high-churn files 290 | 291 | # Gradle 292 | 293 | # Gradle and Maven with auto-import 294 | # When using Gradle or Maven with auto-import, you should exclude module files, 295 | # since they will be recreated, and may cause churn. Uncomment if using 296 | # auto-import. 297 | # .idea/artifacts 298 | # .idea/compiler.xml 299 | # .idea/jarRepositories.xml 300 | # .idea/modules.xml 301 | # .idea/*.iml 302 | # .idea/modules 303 | # *.iml 304 | # *.ipr 305 | 306 | # CMake 307 | 308 | # Mongo Explorer plugin 309 | 310 | # File-based project format 311 | 312 | # IntelliJ 313 | 314 | # mpeltonen/sbt-idea plugin 315 | 316 | # JIRA plugin 317 | 318 | # Cursive Clojure plugin 319 | 320 | # Crashlytics plugin (for Android Studio and IntelliJ) 321 | 322 | # Editor-based Rest Client 323 | 324 | # Android studio 3.1+ serialized cache file 325 | 326 | ### PyCharm+all Patch ### 327 | # Ignores the whole .idea folder and all .iml files 328 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 329 | 330 | .idea/ 331 | 332 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 333 | 334 | *.iml 335 | modules.xml 336 | .idea/misc.xml 337 | *.ipr 338 | 339 | # Sonarlint plugin 340 | .idea/sonarlint 341 | 342 | ### PyCharm+iml ### 343 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 344 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 345 | 346 | # User-specific stuff 347 | 348 | # Generated files 349 | 350 | # Sensitive or high-churn files 351 | 352 | # Gradle 353 | 354 | # Gradle and Maven with auto-import 355 | # When using Gradle or Maven with auto-import, you should exclude module files, 356 | # since they will be recreated, and may cause churn. Uncomment if using 357 | # auto-import. 358 | # .idea/artifacts 359 | # .idea/compiler.xml 360 | # .idea/jarRepositories.xml 361 | # .idea/modules.xml 362 | # .idea/*.iml 363 | # .idea/modules 364 | # *.iml 365 | # *.ipr 366 | 367 | # CMake 368 | 369 | # Mongo Explorer plugin 370 | 371 | # File-based project format 372 | 373 | # IntelliJ 374 | 375 | # mpeltonen/sbt-idea plugin 376 | 377 | # JIRA plugin 378 | 379 | # Cursive Clojure plugin 380 | 381 | # Crashlytics plugin (for Android Studio and IntelliJ) 382 | 383 | # Editor-based Rest Client 384 | 385 | # Android studio 3.1+ serialized cache file 386 | 387 | ### PyCharm+iml Patch ### 388 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 389 | 390 | 391 | ### Python ### 392 | # Byte-compiled / optimized / DLL files 393 | 394 | # C extensions 395 | 396 | # Distribution / packaging 397 | 398 | # PyInstaller 399 | # Usually these files are written by a python script from a template 400 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 401 | 402 | # Installer logs 403 | 404 | # Unit test / coverage reports 405 | 406 | # Translations 407 | 408 | # Django stuff: 409 | 410 | # Flask stuff: 411 | 412 | # Scrapy stuff: 413 | 414 | # Sphinx documentation 415 | 416 | # PyBuilder 417 | 418 | # Jupyter Notebook 419 | 420 | # IPython 421 | 422 | # pyenv 423 | 424 | # pipenv 425 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 426 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 427 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 428 | # install all needed dependencies. 429 | 430 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 431 | 432 | # Celery stuff 433 | 434 | # SageMath parsed files 435 | 436 | # Environments 437 | 438 | # Spyder project settings 439 | 440 | # Rope project settings 441 | 442 | # mkdocs documentation 443 | 444 | # mypy 445 | 446 | # Pyre type checker 447 | 448 | # pytype static type analyzer 449 | 450 | # profiling data 451 | 452 | ### vscode ### 453 | .vscode/* 454 | !.vscode/settings.json 455 | !.vscode/tasks.json 456 | !.vscode/launch.json 457 | !.vscode/extensions.json 458 | *.code-workspace 459 | 460 | # End of https://www.toptal.com/developers/gitignore/api/python,pycharm,pycharm+all,pycharm+iml,vscode,macos,flask 461 | --------------------------------------------------------------------------------