8000 Added postman collection as section5, including code from previous se… · ckdanny/testing-python-apps@00ba3bf · GitHub
[go: up one dir, main page]

Skip to content

Commit 00ba3bf

Browse files
committed
Added postman collection as section5, including code from previous section.
1 parent 178f6fc commit 00ba3bf

35 files changed

+1105
-0
lines changed

section5/stores-rest-api.postman_collection.json

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

section5/video_code/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: uwsgi uwsgi.ini

section5/video_code/__init__.py

Whitespace-only changes.

section5/video_code/app.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import os
2+
3+
from flask import Flask, jsonify
4+
from flask_restful import Api
5+
from flask_jwt import JWT, JWTError
6+
7+
from security import authenticate, identity
8+
from resources.user import UserRegister
9+
from resources.item import Item, ItemList
10+
from resources.store import Store, StoreList
11+
12+
app = Flask(__name__)
13+
14+
app.config['DEBUG'] = True
15+
16+
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///data.db')
17+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
18+
app.config['PROPAGATE_EXCEPTIONS'] = True
19+
app.secret_key = 'jose'
20+
api = Api(app)
21+
22+
jwt = JWT(app, authenticate, identity) # /auth
23+
24+
api.add_resource(Store, '/store/<string:name>')
25+
api.add_resource(Item, '/item/<string:name>')
26+
api.add_resource(ItemList, '/items')
27+
api.add_resource(StoreList, '/stores')
28+
29+
api.add_resource(UserRegister, '/register')
30+
31+
32+
@app.errorhandler(JWTError)
33+
def auth_error(err):
34+
return jsonify({'message': 'Could not authorize. Did you include a valid Authorization header?'}), 401
35+
36+
37+
if __name__ == '__main__':
38+
from db import db
39+
40+
db.init_app(app)
41+
42+
if app.config['DEBUG']:
43+
@app.before_first_request
44+
def create_tables():
45+
db.create_all()
46+
47+
app.run(port=5000)

section5/video_code/db.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flask_sqlalchemy import SQLAlchemy
2+
3+
db = SQLAlchemy()

section5/video_code/models/__init__.py

Whitespace-only changes.

section5/video_code/models/item.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from 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+
store_id = db.Column(db.Integer, db.ForeignKey('stores.id'))
12+
store = db.relationship('StoreModel')
13+
14+
def __init__(self, name, price, store_id):
15+
self.name = name
16+
self.price = price
17+
self.store_id = store_id
18+
19+
def json(self):
20+
return {'name': self.name, 'price': self.price}
21+
22+
@classmethod
23+
def find_by_name(cls, name):
24+
return cls.query.filter_by(name=name).first()
25+
26+
def save_to_db(self):
27+
db.session.add(self)
28+
db.session.commit()
29+
30+
def delete_from_db(self):
31+
db.session.delete(self)
32+
db.session.commit()

section5/video_code/models/store.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from db import db
2+
3+
4+
class StoreModel(db.Model):
5+
__tablename__ = 'stores'
6+
7+
id = db.Column(db.Integer, primary_key=True)
8+
name = db.Column(db.String(80))
9+
10+
items = db.relationship('ItemModel', lazy='dynamic')
11+
12+
def __init__(self, name):
13+
self.name = name
14+
15+
def json(self):
16+
return {'name': self.name, 'items': [item.json() for item in self.items.all()]}
17+
18+
@classmethod
19+
def find_by_name(cls, name):
20+
return cls.query.filter_by(name=name).first()
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()

section5/video_code/models/user.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from 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(80))
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, _id):
25+
return cls.query.filter_by(id=_id).first()

section5/video_code/readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Stores REST Api
2+
3+
This is built with Flask, Flask-RESTful, Flask-JWT, and Flask-SQLAlchemy.
4+
5+
Deployed on Heroku.

section5/video_code/requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Flask
2+
Flask-RESTful
3+
Flask-JWT
4+
Flask-SQLAlchemy
5+
uwsgi
6+
psycopg2

section5/video_code/resources/__init__.py

Whitespace-only changes.

section5/video_code/resources/item.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from flask_restful import Resource, reqparse
2+
from flask_jwt import jwt_required
3+
from models.item import ItemModel
4+
5+
6+
class Item(Resource):
7+
parser = reqparse.RequestParser()
8+
parser.add_argument('price',
9+
type=float,
10+
required=True,
11+
help="This field cannot be left blank!")
12+
parser.add_argument('store_id',
13+
type=int,
14+
required=True,
15+
help="Every item needs a store id.")
16+
17+
@jwt_required()
18+
def get(self, name):
19+
item = ItemModel.find_by_name(name)
20+
if item:
21+
return item.json()
22+
return {'message': 'Item not found'}, 404
23+
24+
def post(self, name):
25+
if ItemModel.find_by_name(name):
26+
return {'message': "An item with name '{}' already exists.".format(name)}, 400
27+
28+
data = Item.parser.parse_args()
29+
30+
item = ItemModel(name, **data)
31+
32+
try:
33+
item.save_to_db()
34+
except:
35+
return {"message": "An error occurred inserting the item."}, 500
36+
37+
return item.json(), 201
38+
39+
def delete(self, name):
40+
item = ItemModel.find_by_name(name)
41+
if item:
42+
item.delete_from_db()
43+
44+
return {'message': 'Item deleted'}
45+
46+
def put(self, name):
47+
data = Item.parser.parse_args()
48+
49+
item = ItemModel.find_by_name(name)
50+
51+
if item is None:
52+
item = ItemModel(name, **data)
53+
else:
54+
item.price = data['price']
55+
56+
item.save_to_db()
57+
58+
return item.json()
59+
60+
61+
class ItemList(Resource):
62+
def get(self):
63+
return {'items': [x.json() for x in ItemModel.query.all()]}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from flask_restful import Resource
2+
from models.store import StoreModel
3+
4+
5+
class Store(Resource):
6+
def get(self, name):
7+
store = StoreModel.find_by_name(name)
8+
if store:
9+
return store.json()
10+
return {'message': 'Store not found'}, 404
11+
12+
def post(self, name):
13+
if StoreModel.find_by_name(name):
14+
return {'message': "A store with name '{}' already exists.".format(name)}, 400
15+
16+
store = StoreModel(name)
17+
try:
18+
store.save_to_db()
19+
except:
20+
return {"message": "An error occurred creating the store."}, 500
21+
22+
return store.json(), 201
23+
24+
def delete(self, name):
25+
store = StoreModel.find_by_name(name)
26+
if store:
27+
store.delete_from_db()
28+
29+
return {'message': 'Store deleted'}
30+
31+
32+
class StoreList(Resource):
33+
def get(self):
34+
return {'stores': [store.json() for store in StoreModel.query.all()]}

section5/video_code/resources/user.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from flask_restful import Resource, reqparse
2+
from models.user import UserModel
3+
4+
5+
class UserRegister(Resource):
6+
parser = reqparse.RequestParser()
7+
parser.add_argument('username',
8+
type=str,
9+
required=True,
10+
help="This field cannot be blank.")
11+
parser.add_argument('password',
12+
type=str,
13+
required=True,
14+
help="This field cannot be blank.")
15+
16+
def post(self):
17+
data = UserRegister.parser.parse_args()
18+
19+
if UserModel.find_by_username(data['username']):
20+
return {"message": "A user with that username already exists"}, 400
21+
22+
user = UserModel(**data)
23+
user.save_to_db()
24+
25+
return {"message": "User created successfully."}, 201

section5/video_code/run.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from app import app
2+
from db import db
3+
4+
db.init_app(app)
5+
6+
7+
@app.before_first_request
8+
def create_tables():
9+
db.create_all()

section5/video_code/runtime.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python-3.5.2

section5/video_code/security.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from werkzeug.security import safe_str_cmp
2+
from models.user import UserModel
3+
4+
5+
def authenticate(username, password):
6+
user = UserModel.find_by_username(username)
7+
if user and safe_str_cmp(user.password, password):
8+
return user
9+
10+
11+
def identity(payload):
12+
user_id = payload['identity']
13+
return UserModel.find_by_id(user_id)

section5/video_code/tests/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
BaseTest
3+
4+
This class should be the parent class to each unit test.
5+
It allows for instantiation of the database dynamically,
6+
and makes sure that it is a new, blank database each time.
7+
"""
8+
9+
from unittest import TestCase
10+
from app import app
11+
from db import db
12+
13+
14+
class BaseTest(TestCase):
15+
SQLALCHEMY_DATABASE_URI = "sqlite://"
16+
17+
@classmethod
18+
def setUpClass(cls):
19+
app.config['SQLALCHEMY_DATABASE_URI'] = BaseTest.SQLALCHEMY_DATABASE_URI
20+
app.config['DEBUG'] = False
21+
with app.app_context():
22+
db.init_app(app)
23+
24+
def setUp(self):
25+
with app.app_context():
26+
db.create_all()
27+
self.app = app.test_client
28+
self.app_context = app.app_context
29+
30+
def tearDown(self):
31+
with app.app_context():
32+
db.session.remove()
33+
db.drop_all()

section5/video_code/tests/integration/__init__.py

Whitespace-only changes.

section5/video_code/tests/integration/models/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from models.item import ItemModel
2+
from models.store import StoreModel
3+
from tests.base_test import BaseTest
4+
5+
6+
class ItemTest(BaseTest):
7+
def test_crud(self):
8+
with self.app_context():
9+
store = StoreModel('test')
10+
store.save_to_db()
11+
item = ItemModel('test', 19.99, 1)
12+
13+
self.assertIsNone(ItemModel.find_by_name('test'), "Found an item with name 'test' before save_to_db")
14+
15+
item.save_to_db()
16+
17+
self.assertIsNotNone(ItemModel.find_by_name('test'),
18+
"Did not find an item with name 'test' after save_to_db")
19+
20+
item.delete_from_db()
21+
22+
self.assertIsNone(ItemModel.find_by_name('test'), "Found an item with name 'test' after delete_from_db")
23+
24+
def test_store_relationship(self):
25+
with self.app_context():
26+
store = StoreModel('test_store')
27+
item = ItemModel('test', 19.99, 1)
28+
29+
store.save_to_db()
30+
item.save_to_db()
31+
32+
self.assertEqual(item.store.name, 'test_store')

0 commit comments

Comments
 (0)
0