KEMBAR78
Rest API using Flask & SqlAlchemy | PDF
REST APIREST API
USING FLASK & SQLALCHEMYUSING FLASK & SQLALCHEMY
Alessandro Cucci
Python Developer, Energee3
REPRESENTATIONAL STATE TRANSFERREPRESENTATIONAL STATE TRANSFER
ROY THOMAS FIELDING - 2010ROY THOMAS FIELDING - 2010
HTTP://WWW.ICS.UCI.EDU/~FIELDING/PUBS/DISSERTATION/REST_ARCH_STYLE.HTMHTTP://WWW.ICS.UCI.EDU/~FIELDING/PUBS/DISSERTATION/REST_ARCH_STYLE.HTM
RESTREST
GUIDING CONSTRAINTSGUIDING CONSTRAINTS
CLIENT-SERVERCLIENT-SERVER
STATELESSSTATELESS
CACHEABLECACHEABLE
LAYERED SYSTEMLAYERED SYSTEM
CODE ON DEMAND (OPTIONAL)CODE ON DEMAND (OPTIONAL)
RESTREST
GUIDING CONSTRAINTSGUIDING CONSTRAINTS
UNIFORM INTERFACEUNIFORM INTERFACE
IDENTIFICATION OF RESOURCESIDENTIFICATION OF RESOURCES
MANIPULATION OF RESOURCES THROUGH REPRESENTATIONSMANIPULATION OF RESOURCES THROUGH REPRESENTATIONS
SELF-DESCRIPTIVE MESSAGESSELF-DESCRIPTIVE MESSAGES
HYPERMEDIA AS THE ENGINE OF APPLICATION STATEHYPERMEDIA AS THE ENGINE OF APPLICATION STATE
REST URI EXAMPLESREST URI EXAMPLES
h�p://myapi.com/customers
h�p://myapi.com/customers/33245
REST ANTI-PATTERNSREST ANTI-PATTERNS
h�p://myapi.com/update_customer&id=12345&
format=json
h�p://myapi.com/customers/12345/update
RELATIONSHIP BETWEEN URL AND HTTPRELATIONSHIP BETWEEN URL AND HTTP
METHODSMETHODS
URL GET PUT POST DELETE
h�p://api.myvinylcollec�on.com
/records/
LIST of records in
collec�on
Method not
allowed.
CREATE a
new entry in
the
collec�on.
DELETE the
en�re
collec�on.
h�p://api.myvinylcollec�on.com
/records/1
RETRIEVE a
representa�on of
the addressed
member of the
collec�on
REPLACE
the
addressed
member of
the
collec�on.
Method not
allowed.
DELETE the
addressed
member of
the
collec�on.
WHAT DO WE NOT CARE FOR THIS EVENINGWHAT DO WE NOT CARE FOR THIS EVENING
Stability & Tes�ng
Long-Term maintainability
Edge Cases
Opera�ons, Caching & Deployment
MY VINYL COLLECTION APIMY VINYL COLLECTION API
HTTP://FLASK.POCOO.ORG/HTTP://FLASK.POCOO.ORG/
HELLO API!HELLO API!
from flask import Flask
app = Flask(__name__)
if __name__ == '__main__':
app.run()
$ python myvinylcollectionapi.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
HELLO API!HELLO API!
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello PyRE!"
if __name__ == '__main__':
app.run()
HELLO API!HELLO API!
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def hello():
return jsonify(data="Hello PyRE!")
if __name__ == '__main__':
app.run()
HTTP GET METHODSHTTP GET METHODS
URL GET
h�p://api.myvinylcollec�on.com
/records
LIST of records in
collec�on
h�p://api.myvinylcollec�on.com
/records/1
RETRIEVE a
representa�on of the
addressed member of
the collec�on
from flask import Flask, jsonify, abort
app = Flask(__name__)
RECORDS = [
{
'id': 0,
'artist': "Queen",
'title': "A Night At The Opera",
'year': "1975",
'label': "EMI"
},
{
'id': 1,
'artist': "Pink Floyd",
'title': "The Dark Side Of The Moon",
'year': "1989",
'label': "EMI"
},
...
]
@app.route("/records")
def get_records():
return jsonify(RECORDS)
@app.route("/records/<int:index>")
def get_record(index):
try:
record = RECORDS[index]
except IndexError:
abort(404)
return jsonify(record)
if __name__ == '__main__':
app.run()
HTTP GET METHODHTTP GET METHOD
$ curl -X GET localhost:5000/records
[
{
"artist": "Queen",
"id": 0,
"label": "EMI",
"title": "A Night At The Opera",
"year": "1975"
},
{
"artist": "Pink Floyd",
"id": 1,
"label": "EMI",
"title": "The Dark Side Of The Moon",
"year": "1989"
}
]
HTTP GET Method
$ curl -X GET localhost:5000/records/1
{
"artist": "Pink Floyd",
"id": 1,
"label": "EMI",
"title": "The Dark Side Of The Moon",
"year": "1989"
}
$ curl -X GET localhost:5000/records/5
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.
If you entered the URL manually please check
your spelling and try again.</p>
JSONIFY THAT ERROR!JSONIFY THAT ERROR!
@app.errorhandler(404)
def page_not_found(error):
return jsonify(
error="Not Found",
status_code=404
), 404
$ curl -X GET localhost:5000/records/5
{
"error": "Not Found",
"status_code": 404
}
GOOD NEWS:GOOD NEWS:
IT WORKS!IT WORKS!
BAD NEWS:BAD NEWS:
STATIC DATASTATIC DATA
SEPARATION OF CONCERNSSEPARATION OF CONCERNS
SQLITESQLITE + DISCOGS.COM+ DISCOGS.COM + PANDAS+ PANDAS
CREATE TABLE "collection" (
`index` INTEGER PRIMARY KEY AUTOINCREMENT,
`Catalog#` TEXT,
`Artist` TEXT,
`Title` TEXT,
`Label` TEXT,
`Format` TEXT,
`Rating` REAL,
`Released` INTEGER,
`release_id` INTEGER,
`CollectionFolder` TEXT,
`Date Added` TEXT,
`Collection Media Condition` TEXT,
`Collection Sleeve Condition` TEXT,
`Collection Notes` REAL
)
CSV COLLECTION EXPORTCSV COLLECTION EXPORT
import pandas
import sqlite3
conn = sqlite3.connect('record_collection.db')
conn.text_factory = sqlite3.Binary
df = pandas.read_csv('collection.csv')
df.to_sql('collection', conn)
OBJECT RELATIONAL MAPPER (ORM)OBJECT RELATIONAL MAPPER (ORM)
HTTP://DOCS.SQLALCHEMY.ORGHTTP://DOCS.SQLALCHEMY.ORG
MODEL.PYMODEL.PY
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy import orm
db = SQLAlchemy()
class Record(db.Model):
__tablename__ = "collection"
index = db.Column(db.Integer, primary_key=True)
Artist = db.Column(db.Text, nullable=False)
Title = db.Column(db.Text, nullable=False)
Label = db.Column(db.Text)
Released = db.Column(db.Text)
def as_dict(self):
columns = orm.class_mapper(self.__class__).mapped_table.c
return {
col.name: getattr(self, col.name)
for col in columns
}
QUERYQUERY
>>> # .all() return a list
>>> all_records = Record.query.all()
>>> len(all_records)
80
>>> # .first() return the first item that matches
>>> record = Record.query.filter(Record.index == 9).first()
>>> record.Title
"Back In Black"
>>> record.Artist
"AC/DC"
>>> record.Released
"1980"
>>> # .filter_by() is a shortcut
>>> record = Record.query.filter_by(index == 6).first()
>>> record.Title
"Hotel California"
from flask import Flask, jsonify
from model import db, Record
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///record_collection.db"
db.init_app(app)
@app.route("/records")
def get_records():
records = [r.as_dict() for r in Record.query.all()]
return jsonify(records)
@app.route("/records/<int:index>")
def get_record(index):
record = Record.query.filter(Record.index == index).first_or_404()
return jsonify(record.as_dict())
$ curl -X GET localhost:5000/records
[
{
"Artist": "The Police",
"index": 0,
"Title": "Reggatta De Blanc"
},
{
"Artist": "The Beatles",
"index": 1,
"Title": "Abbey Road"
},
...
]
$ curl -X GET localhost:5000/records/1
{
"Artist": "The Beatles",
"index": 1,
"Title": "Abbey Road"
}
HTTP POST METHODSHTTP POST METHODS
URL POST
h�p://api.myvinylcollec�on.com
/records/
CREATE a new
entry in the
collec�on.
h�p://api.myvinylcollec�on.com
/records/1
Method not
allowed.
POST ON LOCALHOST:5000/RECORDS/IDPOST ON LOCALHOST:5000/RECORDS/ID
@app.errorhandler(405)
def method_not_allowed(error):
return jsonify(
error="Method Not Allowed",
status_code=405
), 405
from flask import Flask, jsonify, abort, request
...
@app.route("/records/<int:index>", methods=['GET', 'POST'])
def get_record(index):
if request.method == 'POST':
abort(405)
record = Record.query.filter(Record.index == index).first_or_404()
return jsonify(record.as_dict())
$ curl -X POST localhost:5000/records/1
{
"error": "Method Not Allowed",
"status_code": 405
}
INSERT INTO DATABASEINSERT INTO DATABASE
>>> # .add() insert a record
>>> db.session.add(record)
>>> # changes won't be saved until committed!
>>> db.session.commit()
ADDING A RECORD TO MY COLLECTIONADDING A RECORD TO MY COLLECTION
@app.route("/records", methods=['GET', 'POST'])
def get_records():
if request.method == 'POST':
record = Record(**json.loads(request.data))
db.session.add(record)
db.session.commit()
return jsonify(record.as_dict()), 201
records = [r.as_dict() for r in Record.query.all()]
return jsonify(records)
ADDING A RECORD TO MY COLLECTIONADDING A RECORD TO MY COLLECTION
$ curl -i -H "Content-Type: application/json" -X POST localhost:5000/records 
> -d '{"Artist":"Neil Joung", "Title":"Harvest", 
> "Label":"Reprise Records", "Released":"1977"}'
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 11:03:10 GMT
{
"Artist": "Neil Young",
"Label": "Reprise Records",
"Released": "1977",
"Title": "American Stars 'N Bars",
"index": 91
}
HTTP PUT METHODSHTTP PUT METHODS
URL PUT
h�p://api.myvinylcollec�on.com
/records/
Method not allowed.
h�p://api.myvinylcollec�on.com
/records/1
REPLACE the
addressed member
of the collec�on.
@app.route("/records", methods=['GET', 'POST', 'PUT'])
def get_records():
if request.method == 'POST':
record = Record(**json.loads(request.data))
db.session.add(record)
db.session.commit()
return jsonify(record.as_dict()), 201
elif request.method == 'PUT':
abort(405)
records = [r.as_dict() for r in Record.query.all()]
return jsonify(records), 200
@app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT'])
def get_record(index):
if request.method == 'POST':
abort(405)
else:
record = Record.query.filter(Record.index == index).first_or_404()
if request.method == 'PUT':
for k, v in json.loads(request.data).iteritems():
setattr(record, k, v)
db.session.add(record)
db.session.commit()
return jsonify(record.as_dict()), 200
PUT ON COLLECTIONPUT ON COLLECTION
$ curl -i -H "Content-Type: application/json" 
> -X POST localhost:5000/records 
> -d '{"Artist":"Neil Joung", "Title":"Harvest", 
> "Label":"Reprise Records", "Released":"1977"}'
HTTP/1.0 405 METHOD NOT ALLOWED
Content-Type: application/json
Content-Length: 59
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 10:20:06 GMT
{
"error": "Method Not Allowed",
"status_code": 405
}
PUT ON RESOURCEPUT ON RESOURCE
$ curl -i -H "Content-Type: application/json" 
> -X PUT localhost:5000/records/91 
> -d '{"Artist":"Neil Joung", "Title":"Harvest", 
> "Label":"Reprise Records", "Released":"1977"}'
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 11:07:22 GMT
{
"Artist": "Neil Young",
"Label": "Reprise Records",
"Released": "1977",
"Title": "American Stars 'N Bars",
"index": 91
}
HTTP DELETE METHODSHTTP DELETE METHODS
URL DELETE
h�p://api.myvinylcollec�on.com
/records/
DELETE the en�re
collec�on.
h�p://api.myvinylcollec�on.com
/records/1
DELETE the
addressed member
of the collec�on.
DELETE ON COLLECTIONDELETE ON COLLECTION
@app.route("/records", methods=['GET', 'POST', 'PUT', 'DELETE'])
def get_records():
if request.method == 'POST':
record = Record(**json.loads(request.data))
db.session.add(record)
db.session.commit()
return jsonify(record.as_dict()), 201
elif request.method == 'PUT':
abort(405)
records = [r.as_dict() for r in Record.query.all()]
if request.method == 'DELETE':
for r in records:
db.session.delete(r)
db.session.commit()
records = [r.as_dict() for r in Record.query.all()]
return jsonify(records), 200
DELETE ON RESOURCEDELETE ON RESOURCE
@app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT', 'DELETE'])
def get_record(index):
if request.method == 'POST':
abort(405)
else:
record = Record.query.filter(Record.index == index).first_or_404()
if request.method == 'PUT':
for k, v in json.loads(request.data).iteritems():
setattr(record, k, v)
db.session.add(record)
db.session.commit()
elif request.method == 'DELETE':
db.session.delete(record)
db.session.commit()
return jsonify(record.as_dict()), 200
DELETE ON RESOURCEDELETE ON RESOURCE
$ curl -i -X DELETE localhost:5000/records/91
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 10:40:00 GMT
{
"Artist": "Neil Young",
"Label": "Reprise Records",
"Released": "1977",
"Title": "American Stars 'N Bars",
"index": 91
}
DELETE ON RESOURCEDELETE ON RESOURCE
$ curl -i -X DELETE localhost:5000/records/91
HTTP/1.0 HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 50
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 10:40:09 GMT
{
"error": "Not Found",
"status_code": 404
}
DELETE ON COLLECTIONDELETE ON COLLECTION
$ curl -i -X DELETE localhost:5000/records
FLASK-LOGINFLASK-LOGIN
HTTPS://FLASK-LOGIN.READTHEDOCS.IOHTTPS://FLASK-LOGIN.READTHEDOCS.IO
PWD AUTHENTICATIONPWD AUTHENTICATION
from flask import Flask, jsonify, abort
from flask_login import LoginManager, current_user
app = Flask(__name__)
login_manager = LoginManager(app)
@login_manager.request_loader
def check_token(request):
token = request.headers.get('Authorization')
if token == 'L3T_M3_PA55!':
return "You_can_pass" # DON'T TRY THIS AT HOME!
return None
@app.route("/")
def get_main_root():
if current_user:
return jsonify(data='Hello Login'), 200
else:
abort(401)
HOW IT WORKSHOW IT WORKS
$ curl -i localhost:5000
HTTP/1.0 401 UNAUTHORIZED
Content-Type: application/json
WWW-Authenticate: Basic realm="Authentication Required"
Content-Length: 37
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 14:46:55 GMT
{
"error": "Unauthorized access"
}
$ curl -i -H "Authorization: L3T_M3_PA55!" localhost:5000
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 28
Server: Werkzeug/0.11.11 Python/2.7.12+
Date: Sat, 03 Dec 2016 14:42:00 GMT
{
"data": "Hello Login"
}
SECURING OUR API - RESOURCESECURING OUR API - RESOURCE
@app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT', 'DELETE'])
def get_record(index):
if request.method == 'POST':
abort(405)
else:
record = Record.query.filter(Record.index == index).first_or_404()
if request.method == 'PUT':
if current_user:
for k, v in json.loads(request.data).iteritems():
setattr(record, k, v)
db.session.add(record)
db.session.commit()
else:
abort(401)
elif request.method == 'DELETE':
if current_user:
db.session.delete(record)
db.session.commit()
else:
abort(401)
return jsonify(record.as_dict()), 200
SECURING OUR API - COLLECTIONSECURING OUR API - COLLECTION
@app.route("/records", methods=['GET', 'POST', 'PUT', 'DELETE'])
def get_records():
if request.method == 'POST':
record = Record(**json.loads(request.data))
db.session.add(record)
db.session.commit()
return jsonify(record.as_dict()), 201
elif request.method == 'PUT':
abort(405)
records = [r.as_dict() for r in Record.query.all()]
if request.method == 'DELETE':
if current_user:
for r in records:
db.session.delete(r)
db.session.commit()
records = [r.as_dict() for r in Record.query.all()]
return jsonify(records), 200
else:
abort(401)
return jsonify(records), 200
HOMEWORKSHOMEWORKS
Pagina�on with Flask-SqlAlchemy
Rate Limi�ng with Flask-Limiter
Cache with Flask-Cache
THANK YOU!THANK YOU!
{
'slides': 'www.alessandrocucci.it/pyre/restapi',
'code': 'https://goo.gl/4UOqEr'
}

Rest API using Flask & SqlAlchemy

  • 1.
    REST APIREST API USINGFLASK & SQLALCHEMYUSING FLASK & SQLALCHEMY Alessandro Cucci Python Developer, Energee3
  • 2.
    REPRESENTATIONAL STATE TRANSFERREPRESENTATIONALSTATE TRANSFER ROY THOMAS FIELDING - 2010ROY THOMAS FIELDING - 2010 HTTP://WWW.ICS.UCI.EDU/~FIELDING/PUBS/DISSERTATION/REST_ARCH_STYLE.HTMHTTP://WWW.ICS.UCI.EDU/~FIELDING/PUBS/DISSERTATION/REST_ARCH_STYLE.HTM
  • 3.
  • 4.
    RESTREST GUIDING CONSTRAINTSGUIDING CONSTRAINTS UNIFORMINTERFACEUNIFORM INTERFACE IDENTIFICATION OF RESOURCESIDENTIFICATION OF RESOURCES MANIPULATION OF RESOURCES THROUGH REPRESENTATIONSMANIPULATION OF RESOURCES THROUGH REPRESENTATIONS SELF-DESCRIPTIVE MESSAGESSELF-DESCRIPTIVE MESSAGES HYPERMEDIA AS THE ENGINE OF APPLICATION STATEHYPERMEDIA AS THE ENGINE OF APPLICATION STATE
  • 5.
    REST URI EXAMPLESRESTURI EXAMPLES h�p://myapi.com/customers h�p://myapi.com/customers/33245 REST ANTI-PATTERNSREST ANTI-PATTERNS h�p://myapi.com/update_customer&id=12345& format=json h�p://myapi.com/customers/12345/update
  • 6.
    RELATIONSHIP BETWEEN URLAND HTTPRELATIONSHIP BETWEEN URL AND HTTP METHODSMETHODS URL GET PUT POST DELETE h�p://api.myvinylcollec�on.com /records/ LIST of records in collec�on Method not allowed. CREATE a new entry in the collec�on. DELETE the en�re collec�on. h�p://api.myvinylcollec�on.com /records/1 RETRIEVE a representa�on of the addressed member of the collec�on REPLACE the addressed member of the collec�on. Method not allowed. DELETE the addressed member of the collec�on.
  • 7.
    WHAT DO WENOT CARE FOR THIS EVENINGWHAT DO WE NOT CARE FOR THIS EVENING Stability & Tes�ng Long-Term maintainability Edge Cases Opera�ons, Caching & Deployment
  • 8.
    MY VINYL COLLECTIONAPIMY VINYL COLLECTION API
  • 9.
  • 10.
    HELLO API!HELLO API! fromflask import Flask app = Flask(__name__) if __name__ == '__main__': app.run() $ python myvinylcollectionapi.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
  • 12.
    HELLO API!HELLO API! fromflask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello PyRE!" if __name__ == '__main__': app.run()
  • 13.
    HELLO API!HELLO API! fromflask import Flask, jsonify app = Flask(__name__) @app.route("/") def hello(): return jsonify(data="Hello PyRE!") if __name__ == '__main__': app.run()
  • 14.
    HTTP GET METHODSHTTPGET METHODS URL GET h�p://api.myvinylcollec�on.com /records LIST of records in collec�on h�p://api.myvinylcollec�on.com /records/1 RETRIEVE a representa�on of the addressed member of the collec�on
  • 15.
    from flask importFlask, jsonify, abort app = Flask(__name__) RECORDS = [ { 'id': 0, 'artist': "Queen", 'title': "A Night At The Opera", 'year': "1975", 'label': "EMI" }, { 'id': 1, 'artist': "Pink Floyd", 'title': "The Dark Side Of The Moon", 'year': "1989", 'label': "EMI" }, ... ]
  • 16.
    @app.route("/records") def get_records(): return jsonify(RECORDS) @app.route("/records/<int:index>") defget_record(index): try: record = RECORDS[index] except IndexError: abort(404) return jsonify(record) if __name__ == '__main__': app.run()
  • 17.
    HTTP GET METHODHTTPGET METHOD $ curl -X GET localhost:5000/records [ { "artist": "Queen", "id": 0, "label": "EMI", "title": "A Night At The Opera", "year": "1975" }, { "artist": "Pink Floyd", "id": 1, "label": "EMI", "title": "The Dark Side Of The Moon", "year": "1989" } ]
  • 18.
    HTTP GET Method $curl -X GET localhost:5000/records/1 { "artist": "Pink Floyd", "id": 1, "label": "EMI", "title": "The Dark Side Of The Moon", "year": "1989" } $ curl -X GET localhost:5000/records/5 <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
  • 19.
    JSONIFY THAT ERROR!JSONIFYTHAT ERROR! @app.errorhandler(404) def page_not_found(error): return jsonify( error="Not Found", status_code=404 ), 404 $ curl -X GET localhost:5000/records/5 { "error": "Not Found", "status_code": 404 }
  • 20.
    GOOD NEWS:GOOD NEWS: ITWORKS!IT WORKS! BAD NEWS:BAD NEWS: STATIC DATASTATIC DATA
  • 21.
  • 22.
    SQLITESQLITE + DISCOGS.COM+DISCOGS.COM + PANDAS+ PANDAS
  • 23.
    CREATE TABLE "collection"( `index` INTEGER PRIMARY KEY AUTOINCREMENT, `Catalog#` TEXT, `Artist` TEXT, `Title` TEXT, `Label` TEXT, `Format` TEXT, `Rating` REAL, `Released` INTEGER, `release_id` INTEGER, `CollectionFolder` TEXT, `Date Added` TEXT, `Collection Media Condition` TEXT, `Collection Sleeve Condition` TEXT, `Collection Notes` REAL )
  • 24.
    CSV COLLECTION EXPORTCSVCOLLECTION EXPORT
  • 25.
    import pandas import sqlite3 conn= sqlite3.connect('record_collection.db') conn.text_factory = sqlite3.Binary df = pandas.read_csv('collection.csv') df.to_sql('collection', conn)
  • 27.
    OBJECT RELATIONAL MAPPER(ORM)OBJECT RELATIONAL MAPPER (ORM) HTTP://DOCS.SQLALCHEMY.ORGHTTP://DOCS.SQLALCHEMY.ORG
  • 28.
    MODEL.PYMODEL.PY from flask_sqlalchemy importSQLAlchemy from flask_sqlalchemy import orm db = SQLAlchemy() class Record(db.Model): __tablename__ = "collection" index = db.Column(db.Integer, primary_key=True) Artist = db.Column(db.Text, nullable=False) Title = db.Column(db.Text, nullable=False) Label = db.Column(db.Text) Released = db.Column(db.Text) def as_dict(self): columns = orm.class_mapper(self.__class__).mapped_table.c return { col.name: getattr(self, col.name) for col in columns }
  • 29.
    QUERYQUERY >>> # .all()return a list >>> all_records = Record.query.all() >>> len(all_records) 80 >>> # .first() return the first item that matches >>> record = Record.query.filter(Record.index == 9).first() >>> record.Title "Back In Black" >>> record.Artist "AC/DC" >>> record.Released "1980" >>> # .filter_by() is a shortcut >>> record = Record.query.filter_by(index == 6).first() >>> record.Title "Hotel California"
  • 30.
    from flask importFlask, jsonify from model import db, Record app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///record_collection.db" db.init_app(app) @app.route("/records") def get_records(): records = [r.as_dict() for r in Record.query.all()] return jsonify(records) @app.route("/records/<int:index>") def get_record(index): record = Record.query.filter(Record.index == index).first_or_404() return jsonify(record.as_dict())
  • 31.
    $ curl -XGET localhost:5000/records [ { "Artist": "The Police", "index": 0, "Title": "Reggatta De Blanc" }, { "Artist": "The Beatles", "index": 1, "Title": "Abbey Road" }, ... ] $ curl -X GET localhost:5000/records/1 { "Artist": "The Beatles", "index": 1, "Title": "Abbey Road" }
  • 32.
    HTTP POST METHODSHTTPPOST METHODS URL POST h�p://api.myvinylcollec�on.com /records/ CREATE a new entry in the collec�on. h�p://api.myvinylcollec�on.com /records/1 Method not allowed.
  • 33.
    POST ON LOCALHOST:5000/RECORDS/IDPOSTON LOCALHOST:5000/RECORDS/ID @app.errorhandler(405) def method_not_allowed(error): return jsonify( error="Method Not Allowed", status_code=405 ), 405 from flask import Flask, jsonify, abort, request ... @app.route("/records/<int:index>", methods=['GET', 'POST']) def get_record(index): if request.method == 'POST': abort(405) record = Record.query.filter(Record.index == index).first_or_404() return jsonify(record.as_dict())
  • 34.
    $ curl -XPOST localhost:5000/records/1 { "error": "Method Not Allowed", "status_code": 405 }
  • 35.
    INSERT INTO DATABASEINSERTINTO DATABASE >>> # .add() insert a record >>> db.session.add(record) >>> # changes won't be saved until committed! >>> db.session.commit()
  • 36.
    ADDING A RECORDTO MY COLLECTIONADDING A RECORD TO MY COLLECTION @app.route("/records", methods=['GET', 'POST']) def get_records(): if request.method == 'POST': record = Record(**json.loads(request.data)) db.session.add(record) db.session.commit() return jsonify(record.as_dict()), 201 records = [r.as_dict() for r in Record.query.all()] return jsonify(records)
  • 37.
    ADDING A RECORDTO MY COLLECTIONADDING A RECORD TO MY COLLECTION $ curl -i -H "Content-Type: application/json" -X POST localhost:5000/records > -d '{"Artist":"Neil Joung", "Title":"Harvest", > "Label":"Reprise Records", "Released":"1977"}' HTTP/1.0 201 CREATED Content-Type: application/json Content-Length: 104 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 11:03:10 GMT { "Artist": "Neil Young", "Label": "Reprise Records", "Released": "1977", "Title": "American Stars 'N Bars", "index": 91 }
  • 38.
    HTTP PUT METHODSHTTPPUT METHODS URL PUT h�p://api.myvinylcollec�on.com /records/ Method not allowed. h�p://api.myvinylcollec�on.com /records/1 REPLACE the addressed member of the collec�on.
  • 39.
    @app.route("/records", methods=['GET', 'POST','PUT']) def get_records(): if request.method == 'POST': record = Record(**json.loads(request.data)) db.session.add(record) db.session.commit() return jsonify(record.as_dict()), 201 elif request.method == 'PUT': abort(405) records = [r.as_dict() for r in Record.query.all()] return jsonify(records), 200 @app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT']) def get_record(index): if request.method == 'POST': abort(405) else: record = Record.query.filter(Record.index == index).first_or_404() if request.method == 'PUT': for k, v in json.loads(request.data).iteritems(): setattr(record, k, v) db.session.add(record) db.session.commit() return jsonify(record.as_dict()), 200
  • 40.
    PUT ON COLLECTIONPUTON COLLECTION $ curl -i -H "Content-Type: application/json" > -X POST localhost:5000/records > -d '{"Artist":"Neil Joung", "Title":"Harvest", > "Label":"Reprise Records", "Released":"1977"}' HTTP/1.0 405 METHOD NOT ALLOWED Content-Type: application/json Content-Length: 59 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 10:20:06 GMT { "error": "Method Not Allowed", "status_code": 405 }
  • 41.
    PUT ON RESOURCEPUTON RESOURCE $ curl -i -H "Content-Type: application/json" > -X PUT localhost:5000/records/91 > -d '{"Artist":"Neil Joung", "Title":"Harvest", > "Label":"Reprise Records", "Released":"1977"}' HTTP/1.0 200 OK Content-Type: application/json Content-Length: 104 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 11:07:22 GMT { "Artist": "Neil Young", "Label": "Reprise Records", "Released": "1977", "Title": "American Stars 'N Bars", "index": 91 }
  • 42.
    HTTP DELETE METHODSHTTPDELETE METHODS URL DELETE h�p://api.myvinylcollec�on.com /records/ DELETE the en�re collec�on. h�p://api.myvinylcollec�on.com /records/1 DELETE the addressed member of the collec�on.
  • 43.
    DELETE ON COLLECTIONDELETEON COLLECTION @app.route("/records", methods=['GET', 'POST', 'PUT', 'DELETE']) def get_records(): if request.method == 'POST': record = Record(**json.loads(request.data)) db.session.add(record) db.session.commit() return jsonify(record.as_dict()), 201 elif request.method == 'PUT': abort(405) records = [r.as_dict() for r in Record.query.all()] if request.method == 'DELETE': for r in records: db.session.delete(r) db.session.commit() records = [r.as_dict() for r in Record.query.all()] return jsonify(records), 200
  • 44.
    DELETE ON RESOURCEDELETEON RESOURCE @app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT', 'DELETE']) def get_record(index): if request.method == 'POST': abort(405) else: record = Record.query.filter(Record.index == index).first_or_404() if request.method == 'PUT': for k, v in json.loads(request.data).iteritems(): setattr(record, k, v) db.session.add(record) db.session.commit() elif request.method == 'DELETE': db.session.delete(record) db.session.commit() return jsonify(record.as_dict()), 200
  • 45.
    DELETE ON RESOURCEDELETEON RESOURCE $ curl -i -X DELETE localhost:5000/records/91 HTTP/1.0 200 OK Content-Type: application/json Content-Length: 104 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 10:40:00 GMT { "Artist": "Neil Young", "Label": "Reprise Records", "Released": "1977", "Title": "American Stars 'N Bars", "index": 91 }
  • 46.
    DELETE ON RESOURCEDELETEON RESOURCE $ curl -i -X DELETE localhost:5000/records/91 HTTP/1.0 HTTP/1.0 404 NOT FOUND Content-Type: application/json Content-Length: 50 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 10:40:09 GMT { "error": "Not Found", "status_code": 404 }
  • 47.
    DELETE ON COLLECTIONDELETEON COLLECTION $ curl -i -X DELETE localhost:5000/records
  • 49.
  • 50.
    PWD AUTHENTICATIONPWD AUTHENTICATION fromflask import Flask, jsonify, abort from flask_login import LoginManager, current_user app = Flask(__name__) login_manager = LoginManager(app) @login_manager.request_loader def check_token(request): token = request.headers.get('Authorization') if token == 'L3T_M3_PA55!': return "You_can_pass" # DON'T TRY THIS AT HOME! return None @app.route("/") def get_main_root(): if current_user: return jsonify(data='Hello Login'), 200 else: abort(401)
  • 51.
    HOW IT WORKSHOWIT WORKS $ curl -i localhost:5000 HTTP/1.0 401 UNAUTHORIZED Content-Type: application/json WWW-Authenticate: Basic realm="Authentication Required" Content-Length: 37 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 14:46:55 GMT { "error": "Unauthorized access" } $ curl -i -H "Authorization: L3T_M3_PA55!" localhost:5000 HTTP/1.0 200 OK Content-Type: application/json Content-Length: 28 Server: Werkzeug/0.11.11 Python/2.7.12+ Date: Sat, 03 Dec 2016 14:42:00 GMT { "data": "Hello Login" }
  • 52.
    SECURING OUR API- RESOURCESECURING OUR API - RESOURCE @app.route("/records/<int:index>", methods=['GET', 'POST', 'PUT', 'DELETE']) def get_record(index): if request.method == 'POST': abort(405) else: record = Record.query.filter(Record.index == index).first_or_404() if request.method == 'PUT': if current_user: for k, v in json.loads(request.data).iteritems(): setattr(record, k, v) db.session.add(record) db.session.commit() else: abort(401) elif request.method == 'DELETE': if current_user: db.session.delete(record) db.session.commit() else: abort(401) return jsonify(record.as_dict()), 200
  • 53.
    SECURING OUR API- COLLECTIONSECURING OUR API - COLLECTION @app.route("/records", methods=['GET', 'POST', 'PUT', 'DELETE']) def get_records(): if request.method == 'POST': record = Record(**json.loads(request.data)) db.session.add(record) db.session.commit() return jsonify(record.as_dict()), 201 elif request.method == 'PUT': abort(405) records = [r.as_dict() for r in Record.query.all()] if request.method == 'DELETE': if current_user: for r in records: db.session.delete(r) db.session.commit() records = [r.as_dict() for r in Record.query.all()] return jsonify(records), 200 else: abort(401) return jsonify(records), 200
  • 54.
    HOMEWORKSHOMEWORKS Pagina�on with Flask-SqlAlchemy RateLimi�ng with Flask-Limiter Cache with Flask-Cache
  • 55.
    THANK YOU!THANK YOU! { 'slides':'www.alessandrocucci.it/pyre/restapi', 'code': 'https://goo.gl/4UOqEr' }