75
Jeff Knupp @jeffknupp jeff@jeffknupp.com Wharton Web Conference 2014

Building Automated REST APIs with Python

Embed Size (px)

DESCRIPTION

Writing REST APIs with ORMs and web frameworks is a chore. I'm lazy, and I don't want to write boring code. In this talk, I'll go over what REST APIs are, why they're useful, and why we should never have to write one from scratch again. By the end of this talk, we'll have achieved developer Nirvana: a RESTful API service and Admin interface for existing databases *without writing any code*.

Citation preview

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