Transcript
Page 1: Building Automated REST APIs with Python

JeffKnupp@[email protected]

Page 2: Building Automated REST APIs with Python

Authorof“WritingIdiomaticPython”Full-timePythondeveloper@AppNexusBloggeratjeffknupp.comCreatorofthe“sandman”Pythonlibrary

Page 3: Building Automated REST APIs with Python

We'regoingtousePythonto generateaRESTAPI.

Andwe'regoingtodoitwithoutwritingasinglelineofcode.

Page 4: Building Automated REST APIs with Python

We'llgooverwhataRESTAPIis,howitworks,andwhyit'susefulWe'llreviewtheHTTPprotocolandhowthewebworksWe'llseealotofPythoncode

Page 5: Building Automated REST APIs with Python

Sevenletters.Twoacronyms.Buywhatdoesitmean?

Page 6: Building Automated REST APIs with Python

Programmaticwayofinteractingwithathird-partysystem.

Page 7: Building Automated REST APIs with Python

WaytointeractwithAPIsoverHTTP(thecommunicationprotocoltheInternetisbuilton).

"REST"wascoinedbyRoyFieldinginhis2000doctoraldissertation.Includessetofdesignprinciplesandbestpracticesfordesigningsystemsmeanttobe"RESTful".

Page 8: Building Automated REST APIs with Python

InRESTfulsystems,applicationstateismanipulatedbytheclientinteractingwithhyperlinks.Arootlink(e.g.

)describeswhatactionscanbetakenbylistingresourcesandstateashyperlinks.

http://example.com/api/

Page 9: Building Automated REST APIs with Python

HTTPisjustamessagingprotocol.HappenstobetheonetheInternetisbasedon.

RESTfulsystemsusethisprotocoltotheiradvantagee.g.cachingresources

Page 10: Building Automated REST APIs with Python

GETPOSTPUTPATCHDELETE

Page 11: Building Automated REST APIs with Python

TounderstandhowRESTAPIswork,wehavetounderstandhowthewebworks.

EverythingyouseeonthewebistransferredtoyourcomputerusingHTTP.

Page 12: Building Automated REST APIs with Python

Whathappenswhenwetypehttp://www.jeffknupp.comintoourbrowser?

Let'stracethelifecycleofabrowser'srequest.

Page 13: Building Automated REST APIs with Python

AprotocolcalledtheDomainNameService(DNS)isusedtofindthe"real"(IP)addressofjeffknupp.com.

Page 14: Building Automated REST APIs with Python

GET

ThebrowsersendsaGETrequestto192.168.1.1forthepageataddress/(thehomeor"root"page).

Page 15: Building Automated REST APIs with Python

The (aprogramusedtoserviceHTTPrequeststoawebsite)receivestherequest,findstheassociatedHTMLfile,andsendsitasanHTTPResponse.

Page 16: Building Automated REST APIs with Python

Ifthereareanyimages,videos,orscriptsthattheHTMLmakesreferenceto,separateHTTPGETrequestsaremade

forthoseaswell.

Page 17: Building Automated REST APIs with Python

Programs,likecurl,canalsoissueHTTPrequests

Page 18: Building Automated REST APIs with Python

CURL

curltalkstothewebserver,usingapublicAPI(viaHTTP)

Page 19: Building Automated REST APIs with Python

ARESTAPIexposesyourinternalsystemtotheoutsideworld

Page 20: Building Automated REST APIs with Python

It'salsoafantasticwaytomakeasystemavailabletoother,internalsystemswithinanorganization.

Page 21: Building Automated REST APIs with Python

ExamplesofpopularRESTAPIs:TwitterGitHubGoogle(foralmostallservices)

Page 22: Building Automated REST APIs with Python

Ifyou'reaSaaSprovider,youareexpectedtohaveaRESTAPIforpeopletowriteprogramstointeractwithyour

service.

Page 23: Building Automated REST APIs with Python

FourcoreconceptsarefundamentaltoallRESTservices(courtesyWikipedia)

Page 24: Building Automated REST APIs with Python

WhenusingHTTP,thisisdoneusingaURI.Importantly,aresourceand arecompletelyorthogonal.Theserverdoesn'treturndatabaseresultsbutratherthe

JSONorXMLorHTMLrepresentationoftheresource.

Page 25: Building Automated REST APIs with Python

Whentheservertransmitstherepresentationoftheresourcetotheclient,itincludesenoughinformationforthe

clienttoknowhowtomodifyordeletetheresource.

Page 26: Building Automated REST APIs with Python

Eachrepresentationreturnedbytheserverincludesinformationonhowtoprocessthemessage(e.g.usingMIME

types

Page 27: Building Automated REST APIs with Python

Clientsare .Theyknownothingabouthowtheserviceislaidouttobeginwith.Theydiscoverwhatactionstheycan

takefromtherootlink.Followingalinkgivesfurtherlinks,definingexactlywhatmaybedonefromthatresource.

Clientsaren'tassumedtoknow exceptwhatthemessagecontainsandwhattheserveralreadytoldthem.

Page 28: Building Automated REST APIs with Python

ARESTAPIallows tosend tomanipulate .

...SoweneedtowriteaservercapableofacceptingHTTPrequests,actingonthem,andreturningHTTPresponses.

Page 29: Building Automated REST APIs with Python

Yep.ARESTfulAPIServiceisjustawebapplicationand,assuch,isbuiltusingthesamesetoftools.We'llbuildours

usingPython,Flask,andSQLAlchemy

Page 30: Building Automated REST APIs with Python

EarlierwesaidaRESTAPIallowsclientstomanipulateviaHTTP.

Page 31: Building Automated REST APIs with Python

Prettymuch.Ifyou'resystemisbuiltusingORMmodels,yourresourcesarealmostcertainlygoingtobeyourmodels.

Page 32: Building Automated REST APIs with Python

Webframeworksreducetheboilerplaterequiredtocreateawebapplicationbyproviding:

ofHTTPrequeststohandlerfunctionsorclassesExample:/foo=>defprocess_foo()

ofHTTPresponsestoinjectdynamicdatainpre-definedstructure

Example:<h1>Hello{{user_name}}</h1>

Page 33: Building Automated REST APIs with Python

ThemoretimeyouspendbuildingRESTAPIswithwebframeworks,themoreyou'llnoticethesubtle(andattimes,

glaring)impedancemismatch.

URLsas toprocessingfunctions;RESTAPIstreatURLsastheaddressofaresourceorcollectionHTMLtemplating,whileRESTAPIsrarely.JSON-relatedfunctionalityfeelsbolted-on.

Page 34: Building Automated REST APIs with Python

Imaginewe'reTwitterweeksafterlaunch.AshtonKutcherseemstobeabletouseourservice,butwhatabout

?

That'sright,we'llneedtocreateanAPI.Beinganinternetcompany,we'llbuildaRESTAPIservice.Fornow,we'llfocus

ontworesources:usertweet

Page 35: Building Automated REST APIs with Python

Allresourcesmustbeidentifiedbyauniqueaddressatwhichtheycanbereached,theirURI.Thisrequireseachresource

containauniqueID,usuallyamonotonicallyincreasingintegerorUUID(likeaprimarykeyinadatabasetable).

OurpatternforbuildingURLswillbe/resource_name[/resource_id[/resource_attribute]]

Page 36: Building Automated REST APIs with Python

Herewedefineourresourcesisafilecalledmodels.py:

classUser(db.Model,SerializableModel):__tablename__='user'

id=db.Column(db.Integer,primary_key=True)username=db.Column(db.String)

classTweet(db.Model,SerializableModel):__tablename__='tweet'

id=db.Column(db.Integer,primary_key=True)content=db.Column(db.String)posted_at=db.Column(db.DateTime)user_id=db.Column(db.Integer,db.ForeignKey('user.id'))user=db.relationship(User)

Page 37: Building Automated REST APIs with Python

classSerializableModel(object):"""ASQLAlchemymodelmixinclassthatcanserializeitselfasJSON."""

defto_dict(self):"""Returndictrepresentationofclassbyiteratingoverdatabasecolumns."""value={}forcolumninself.__table__.columns:attribute=getattr(self,column.name)ifisinstance(attribute,datetime.datetime):attribute=str(attribute)value[column.name]=attributereturnvalue

Page 38: Building Automated REST APIs with Python

Here'sthecodethathandlesretrievingasingletweetandreturningitasJSON:

frommodelsimportTweet,User

@app.route('/tweets/<int:tweet_id>',methods=['GET'])defget_tweet(tweet_id):tweet=Tweet.query.get(tweet_id)iftweetisNone:response=jsonify({'result':'error'})response.status_code=404returnresponseelse:returnjsonify({'tweet':tweet.to_dict()})

Page 39: Building Automated REST APIs with Python

Let'scurlournewAPI(preloadedwithasingletweetanduser):

$curllocalhost:5000/tweets/1{"tweet":{"content":"Thisisawesome","id":1,"posted_at":"2014-07-0512:00:00","user_id":1}}

Page 40: Building Automated REST APIs with Python

@app.route('/tweets/',methods=['POST'])defcreate_tweet():"""CreateanewtweetobjectbasedontheJSONdatasentintherequest."""ifnotall(('content','posted_at','user_id'inrequest.json)):response=jsonify({'result':'ERROR'})response.status_code=400#HTTP400:BADREQUESTreturnresponseelse:tweet=Tweet(content=request.json['content'],posted_at=datetime.datetime.strptime(request.json['posted_at'],'%Y-%m-%d%H:%M:%S'),user_id=request.json['user_id'])db.session.add(tweet)db.session.commit()returnjsonify(tweet.to_dict())

Page 41: Building Automated REST APIs with Python

InRESTAPIs,agroupofresourcesiscalleda .RESTAPIsareheavilybuiltonthenotionofresourcesand

collections.Inourcase,the oftweetsisalistofalltweetsinthesystem.

ThetweetcollectionisaccessedbythefollowingURL(accordingtoourrules,describedearlier):/tweets.

Page 42: Building Automated REST APIs with Python

@app.route('/tweets',methods=['GET'])defget_tweet_collection():"""ReturnalltweetsasJSON."""all_tweets=[]fortweetinTweet.query.all():all_tweets.append({'content':tweet.content,'posted_at':tweet.posted_at,'posted_by':tweet.user.username})

Page 43: Building Automated REST APIs with Python

Allthecodethusfarhasbeenprettymuchboilerplate.EveryRESTAPIyouwriteinFlask(modulobusinesslogic)willlook

identical.Howcanweusethattoouradvantage?

Page 44: Building Automated REST APIs with Python

Wehaveself-drivingcarsanddeliverydrones,whycan'twebuildRESTAPIsautomatically?

Page 45: Building Automated REST APIs with Python

Thisallowsonetoworkatahigherlevelofabstraction.Solvetheproblemonceinageneralwayandletcodegeneration

solveeachindividualinstanceoftheproblem.

Partof

Page 46: Building Automated REST APIs with Python

SANDBOY

ThirdpartyFlaskextensionwrittenbythedashingJeffKnupp.Defineyourmodels.Hitabutton.BAM!RESTfulAPI

servicethat .

(Thenamewillmakemoresenseinafewminutes)

Page 47: Building Automated REST APIs with Python

GeneralizesRESTresourcehandlingintonotionofa(e.g.the"TweetService"handlesalltweet-relatedactions).classService(MethodView):"""Baseclassforallresources."""

__model__=None__db__=None

defget(self,resource_id=None):"""ReturnresponsetoHTTPGETrequest."""ifresource_idisNone:returnself._all_resources()else:resource=self._resource(resource_id)ifnotresource:raiseNotFoundExceptionreturnjsonify(resource.to_dict())

Page 48: Building Automated REST APIs with Python

def_all_resources(self):"""ReturnallresourcesofthistypeasaJSONlist."""ifnot'page'inrequest.args:resources=self.__db__.session.query(self.__model__).all()else:resources=self.__model__.query.paginate(int(request.args['page'])).itemsreturnjsonify({'resources':[resource.to_dict()forresourceinresources]})

Page 49: Building Automated REST APIs with Python

Here'showPOSTworks.Noticetheverify_fieldsdecoratoranduseof**request.jsonmagic...

@verify_fieldsdefpost(self):"""ReturnresponsetoHTTPPOSTrequest."""resource=self.__model__.query.filter_by(

**request.json).first()ifresource:returnself._no_content_response()instance=self.__model__(**request.json)self.__db__.session.add(instance)self.__db__.session.commit()returnself._created_response(instance.to_dict())

Page 50: Building Automated REST APIs with Python

Wehaveourmodelsdefined.HowdowetakeadvantageofthegenericServiceclassandcreateservicesfromour

models?defregister(self,cls_list):"""RegisteraclasstobegivenaRESTAPI."""forclsincls_list:serializable_model=type(cls.__name__+'Serializable',(cls,SerializableModel),{})new_endpoint=type(cls.__name__+'Endpoint',(Service,),{'__model__':serializable_model,'__db__':self.db})view_func=new_endpoint.as_view(new_endpoint.__model__.__tablename__)self.blueprint.add_url_rule('/'+new_endpoint.__model__.__tablename__,view_func=view_func)self.blueprint.add_url_rule('/{resource}/<resource_id>'.format(resource=new_endpoint.__model__.__tablename__),view_func=view_func,methods=[

Page 51: Building Automated REST APIs with Python

'GET','PUT','DELETE','PATCH','OPTIONS'])

TYPE

InPython,typewithoneargumentreturnsavariable'stype.Withthreearguments,

.

Page 52: Building Automated REST APIs with Python

TYPE

serializable_model=type(cls.__name__+'Serializable',(cls,SerializableModel),{})

new_endpoint=type(cls.__name__+'Endpoint',(Service,),{'__model__':serializable_model,'__db__':self.db})

Page 53: Building Automated REST APIs with Python

Let'splaypretendagain.Nowwe'reaIaaScompanythatletsusersbuildprivateclouds.We'llfocusontworesources:

cloudandmachine

Page 54: Building Automated REST APIs with Python

classCloud(db.Model):__tablename__='cloud'

id=db.Column(db.Integer,primary_key=True)name=db.Column(db.String,nullable=False)description=db.Column(db.String,nullable=False)

classMachine(db.Model):__tablename__='machine'

id=db.Column(db.Integer,primary_key=True)hostname=db.Column(db.String)operating_system=db.Column(db.String)description=db.Column(db.String)cloud_id=db.Column(db.Integer,db.ForeignKey('cloud.id'))cloud=db.relationship('Cloud')is_running=db.Column(db.Boolean,default=False)

Page 55: Building Automated REST APIs with Python

fromflaskimportFlaskfromflask.ext.sandboyimportSandboyfrommodelsimportMachine,Cloud,db

app=Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///db.sqlite3'db.init_app(app)withapp.app_context():db.create_all()sandboy=Sandboy(app,db,[Machine,Cloud])app.run(debug=True)

Page 56: Building Automated REST APIs with Python
Page 57: Building Automated REST APIs with Python

Incaseswherewe'rebuildingaRESTAPIfromscratch,thisisprettyeasy.Butwhatif:

WehaveanexistingdatabaseWewanttocreateaRESTfulAPIforitIthas200tables

Page 58: Building Automated REST APIs with Python

OnlydownsideofFlask-Sandboyisyouhavetodefineyourmodelclassesexplicitly.Ifyouhavealotofmodels,this

wouldbetedious.

...Idon'tdotedious

Page 59: Building Automated REST APIs with Python

Wehaveprivatecompaniesbuildingrocketshipsandelectriccars.Whycan'twehaveatoolthatyoupointatanexistingdatabaseandhitabutton,then,BLAM!RESTfulAPIservice.

Page 60: Building Automated REST APIs with Python

SANDMAN

,alibrarybyteenheartthrobJeffKnupp,createsaRESTfulAPIservicefor with

.

Page 61: Building Automated REST APIs with Python

Here'showyourunsandmanagainstamysqldatabase:

$sandmanctlmysql+mysqlconnector://localhost/Chinook*Runningonhttp://0.0.0.0:8080/*Restartingwithreloader

Page 62: Building Automated REST APIs with Python

$curl-vlocalhost:8080/artists?Name=AC/DCHTTP/1.0200OKContent-Type:application/jsonDate:Sun,06Jul201415:55:21GMTETag:"cea5dfbb05362bd56c14d0701cedb5a7"Link:</artists/1>;rel="self"

{"ArtistId":1,"Name":"AC/DC","links":[{"rel":"self","uri":"/artists/1"}],"self":"/artists/1"}

Page 63: Building Automated REST APIs with Python

ETagsetcorrectly,allowingforcachingresponsesLinkHeadersettoletclientsdiscoverlinkstootherresourcesSearchenabledbysendinginanattributenameandvalue

Wildcardsearchingsupported

Page 64: Building Automated REST APIs with Python

Wecancurl/andgetalistofallavailableservicesandtheirURLs.Wecanhit/<resource>/metatogetmeta-infoaboutthe

service.Example(the"artist"service):

$curl-vlocalhost:8080/artists/metaHTTP/1.0200OKContent-Length:80Content-Type:application/jsonDate:Sun,06Jul201416:04:25GMTETag:"872ea9f2c6635aa3775dc45aa6bc4975"Server:Werkzeug/0.9.6Python/2.7.6

{"Artist":{"ArtistId":"integer(11)","Name":"varchar(120)"}}

Page 65: Building Automated REST APIs with Python

Andnowfora(probablybroken)live-demo!

Page 66: Building Automated REST APIs with Python

"Real"RESTAPIsenableclientstousetheAPIusingonlytheinformationreturnedfromHTTPrequests.sandmantriestobeas"RESTful"aspossiblewithoutrequiringanycodefrom

theuser.

Page 67: Building Automated REST APIs with Python

WouldbenicetobeabletovisualizeyourdatainadditiontointeractingwithitviaRESTAPI.

Page 68: Building Automated REST APIs with Python
Page 69: Building Automated REST APIs with Python

1. Codegeneration2. Databaseintrospection3. Lotsofmagic

Page 70: Building Automated REST APIs with Python

sandmancamefirst.HasbeennumberonePythonprojectonGitHubmultipletimesandisdownloaded25,000timesa

month.Flask-Sandboyissandman'slittlebrother...

Page 71: Building Automated REST APIs with Python

ThefactthattheendresultisaRESTAPIisnotespeciallyinterestingMoreimportantaretheconceptsunderpinningsandmanandFlask-Sandboy

Page 72: Building Automated REST APIs with Python

WorkathigherlevelofabstractionSolveaproblemonceinagenericmannerReduceserrors,improvesperformance

Ingeneral:

Speakingofautomation,here'showmybookis"built"...

Page 73: Building Automated REST APIs with Python

sandman=Flask+SQLAlchemy+LotsofGlueRequiresyouknowthecapabilitiesofyourtoolsPartoftheUNIXPhilosophy

Page 74: Building Automated REST APIs with Python

ThebestprogrammingadviceIevergotwasto"belazy"

SandmanexistsbecauseIwastoolazytowriteboilerplateORMcodeforanexistingdatabaseFlask-SandboyexistsbecauseIwastoolazytowritethesameAPIservicesoverandoverBeinglazyforcesyoutolearnyourtoolsandmakeheavyuseofthem

Page 75: Building Automated REST APIs with Python

Contactmeat:[email protected]@jeffknupponTwitter

onthetubeshttp://www.jeffknupp.com