416

Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

  • Upload
    others

  • View
    30

  • Download
    1

Embed Size (px)

Citation preview

Page 1: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select
Page 2: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BuildingRESTfulPythonWebServices

Page 3: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TableofContents

BuildingRESTfulPythonWebServicesCreditsAbouttheAuthorAcknowledgmentsAbouttheReviewerwww.PacktPub.com

Whysubscribe?Preface

WhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport

DownloadingtheexamplecodeErrataPiracyQuestions

1.DevelopingRESTfulAPIswithDjangoDesigningaRESTfulAPItointeractwithasimpleSQLitedatabaseUnderstandingthetasksperformedbyeachHTTPmethodWorkingwithlightweightvirtualenvironmentsSettingupthevirtualenvironmentwithDjangoRESTframeworkCreatingthemodelsManagingserializationanddeserializationWritingAPIviewsMakingHTTPrequeststotheAPI

Workingwithcommand-linetools-curlandhttpieWorkingwithGUItools-Postmanandothers

TestyourknowledgeSummary

2.WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjangoUsingmodelserializerstoeliminateduplicatecodeWorkingwithwrapperstowriteAPIviewsUsingthedefaultparsingandrenderingoptionsandmovebeyondJSONBrowsingtheAPIDesigningaRESTfulAPItointeractwithacomplexPostgreSQLdatabaseUnderstandingthetasksperformedbyeachHTTPmethodDeclaringrelationshipswiththemodelsManagingserializationanddeserializationwithrelationshipsandhyperlinksCreatingclass-basedviewsandusinggenericclasses

Page 4: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TakingadvantageofgenericclassbasedviewsWorkingwithendpointsfortheAPICreatingandretrievingrelatedresourcesTestyourknowledgeSummary

3.ImprovingandAddingAuthenticationtoanAPIWithDjangoAddinguniqueconstraintstothemodelsUpdatingasinglefieldforaresourcewiththePATCHmethodTakingadvantageofpaginationCustomizingpaginationclassesUnderstandingauthentication,permissionsandthrottlingAddingsecurity-relateddatatothemodelsCreatingacustomizedpermissionclassforobject-levelpermissionsPersistingtheuserthatmakesarequestConfiguringpermissionpoliciesSettingadefaultvalueforanewrequiredfieldinmigrationsComposingrequestswiththenecessaryauthenticationBrowsingtheAPIwithauthenticationcredentialsTestyourknowledgeSummary

4.Throttling,Filtering,Testing,andDeployinganAPIwithDjangoUnderstandingthrottlingclassesConfiguringthrottlingpoliciesTestingthrottlingpoliciesUnderstandingfiltering,searching,andorderingclassesConfiguringfiltering,searching,andorderingforviewsTestingfiltering,searching,andorderingFiltering,searching,andorderingintheBrowsableAPISettingupunittestsWritingafirstroundofunittestsRunningunittestsandcheckingtestingcoverageImprovingtestingcoverageUnderstandingstrategiesfordeploymentsandscalabilityTestyourknowledgeSummary

5.DevelopingRESTfulAPIswithFlaskDesigningaRESTfulAPItointeractwithasimpledatasourceUnderstandingthetasksperformedbyeachHTTPmethodSettingupavirtualenvironmentwithFlaskandFlask-RESTfulDeclaringstatuscodesfortheresponsesCreatingthemodelUsingadictionaryasarepositoryConfiguringoutputfieldsWorkingwithresourcefulroutingontopofFlaskpluggableviews

Page 5: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConfiguringresourceroutingandendpointsMakingHTTPrequeststotheFlaskAPI

Workingwithcommand-linetoolsâcurlandhttpieWorkingwithGUItools-Postmanandothers

TestyourknowledgeSummary

6.WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlaskDesigningaRESTfulAPItointeractwithaPostgreSQLdatabaseUnderstandingthetasksperformedbyeachHTTPmethodInstallingpackagestosimplifyourcommontasksCreatingandconfiguringthedatabaseCreatingmodelswiththeirrelationshipsCreatingschemastovalidate,serialize,anddeserializemodelsCombiningblueprintswithresourcefulroutingRegisteringtheblueprintandrunningmigrationsCreatingandretrievingrelatedresourcesTestyourknowledgeSummary

7.ImprovingandAddingAuthenticationtoanAPIwithFlaskImprovinguniqueconstraintsinthemodelsUpdatingfieldsforaresourcewiththePATCHmethodCodingagenericpaginationclassAddingpaginationfeaturesUnderstandingthestepstoaddauthenticationandpermissionsAddingausermodelCreatingaschemastovalidate,serialize,anddeserializeusersAddingauthenticationtoresourcesCreatingresourceclassestohandleusersRunningmigrationstogeneratetheusertableComposingrequestswiththenecessaryauthenticationTestyourknowledgeSummary

8.TestingandDeployinganAPIwithFlaskSettingupunittestsWritingafirstroundofunittestsRunningunittestswithnose2andcheckingtestingcoverageImprovingtestingcoverageUnderstandingstrategiesfordeploymentsandscalabilityTestyourknowledgeSummary

9.DevelopingRESTfulAPIswithTornadoDesigningaRESTfulAPItointeractwithslowsensorsandactuatorsUnderstandingthetasksperformedbyeachHTTPmethodSettingupavirtualenvironmentwithTornado

Page 6: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DeclaringstatuscodesfortheresponsesCreatingtheclassesthatrepresentadroneWritingrequesthandlersMappingURLpatternstorequesthandlersMakingHTTPrequeststotheTornadoAPI

Workingwithcommand-linetoolsâcurlandhttpieWorkingwithGUItools-Postmanandothers

TestyourknowledgeSummary

10.WorkingwithAsynchronousCode,Testing,andDeployinganAPIwithTornadoUnderstandingsynchronousandasynchronousexecutionRefactoringcodetotakeadvantageofasynchronousdecoratorsMappingURLpatternstoasynchronousrequesthandlersMakingHTTPrequeststotheTornadonon-blockingAPISettingupunittestsWritingafirstroundofunittestsRunningunittestswithnose2andcheckingtestingcoverageImprovingtestingcoverageOtherPythonWebframeworksforbuildingRESTfulAPIsTestyourknowledgeSummary

11.ExerciseAnswersChapter1,DevelopingRESTfulAPIswithDjangoChapter2,WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjangoChapter3,ImprovingandAddingAuthenticationtoanAPIWithDjangoChapter4,Throttling,Filtering,Testing,andDeployinganAPIwithDjangoChapter5,DevelopingRESTfulAPIswithFlaskChapter6,WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlaskChapter7,ImprovingandAddingAuthenticationtoanAPIwithFlaskChapter8,TestingandDeployinganAPIwithFlaskChapter9,DevelopingRESTfulAPIswithTornadoChapter10,WorkingwithAsynchronousCode,Testing,andDeployinganAPIwith

Tornado

Page 7: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BuildingRESTfulPythonWebServices

Page 8: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BuildingRESTfulPythonWebServicesCopyright©2016PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:October2016

Productionreference:1201016

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

Birmingham

B32PB,UK.

ISBN978-1-78646-225-1

www.packtpub.com

Page 9: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Credits

Author

GastónC.Hillar

CopyEditor

SnehaSingh

Reviewer

ElmerThomas

ProjectCoordinator

SheejalShah

CommissioningEditor

AaronLazar

Proofreader

SafisEditing

AcquisitionEditor

ReshmaRaman

Indexer

RekhaNair

ContentDevelopmentEditor

DivijKotian

Graphics

JasonMonteiro

TechnicalEditor

GebinGeorge

ProductionCoordinator

MelwynDsa

Page 10: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AbouttheAuthorGastónC.HillarisItalianandhasbeenworkingwithcomputerssincehewaseight.HebeganprogrammingwiththelegendaryTexasTI-99/4AandCommodore64homecomputersintheearly80s.HehasaBachelor'sdegreeinComputerSciencefromwhichhegraduatedwithhonors,andanMBAfromwhichhegraduatedwithanoutstandingthesis.Atpresent,GastónisanindependentITconsultantandfreelanceauthorwhoisalwayslookingfornewadventuresaroundtheworld.

HehasbeenaseniorcontributingeditoratDr.Dobb’sandhaswrittenmorethanahundredarticlesonsoftwaredevelopmenttopics.GastonwasalsoaformerMicrosoftMVPintechnicalcomputing.HehasreceivedtheprestigiousIntel®BlackBeltSoftwareDeveloperawardeighttimes.

HeisaguestbloggeratIntel®SoftwareNetwork(http://software.intel.com).Youcanreachhimatgastonhillar@hotmail.comandfollowhimonTwitterathttp://twitter.com/gastonhillar.Gastón'sblogishttp://csharpmulticore.blogspot.com.

Heliveswithhiswife,Vanesa,andhistwosons,KevinandBrandon.

Page 11: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AcknowledgmentsAtthetimeofwritingthisbook,IwasfortunatetoworkwithanexcellentteamatPacktPublishing,whosecontributionsvastlyimprovedthepresentationofthisbook.ReshmaRamanandAaronLazarallowedmetoprovidethemideastodevelopthisbookandIjumpedintotheexcitingprojectofteachinghowtousemanypopularwebframeworkstodevelopRESTfulWebServiceswithPython3.5.DivijKotianhelpedmerealizemyvisionforthisbookandprovidedmanysensiblesuggestionsregardingthetext,theformatandtheflow.Thereaderwillnoticehisgreatwork.ItwasgreatworkingwithDivijinanotherbook.Infact,itisthethirdbookinwhichIwasabletoworkwithReshmaandDivij.It’sbeengreatworkingwiththeminanotherprojectandIcan’twaittoworkwiththemagain.Iwouldliketothankmytechnicalreviewersandproofreaders,fortheirthoroughreviewsandinsightfulcomments.Iwasabletoincorporatesomeoftheknowledgeandwisdomtheyhavegainedintheirmanyyearsinthesoftwaredevelopmentindustry.Thisbookwaspossiblebecausetheygavevaluablefeedback.

GebinGeorgedidawonderfuljobwhenthebookmovedintotheproductionstage.Hehasmadeallthenecessaryadjustmentstogeneratethefinalversionofthebookwithanoutstandinglayout.GebinmadethebookeasytoreadinitsdifferentversionsandmadesureIwashappywiththeresults.Abooklikethisonewithsomanytables,figures,piecesofcode,commandsandsampleoutputsrequiresskilledpeoplewitheyefordetailduringallthestages.IwasfortunatetohaveGebinonboard.Iwouldliketothankmytechnicalreviewersandproofreaders,fortheirthoroughreviewsandinsightfulcomments.Iwasabletoincorporatesomeoftheknowledgeandwisdomtheyhavegainedintheirmanyyearsinthesoftwaredevelopmentindustry.Thisbookwaspossiblebecausetheygavevaluablefeedback.

IusuallystartwritingnotesaboutideasforabookwhenIspendtimeatsoftwaredevelopmentconferencesandevents.IwrotetheinitialideaforthisbookinSanFrancisco,California,atIntelDeveloperForum2015.Oneyearlater,atIntelDeveloperForum2016,IhadthechancetodiscusswithmanysoftwareengineersthebookIwasfinishingandincorporatetheirsuggestionsinthefinaldrafts.

Theentireprocessofwritingabookrequiresahugeamountoflonelyhours.Iwouldn’tbeabletowriteanentirebookwithoutdedicatingsometimetoplaysocceragainstmysonsKevinandBrandon,andmynephew,Nicolas.Ofcourse,Ineverwonamatch.However,Ididscoreafewgoals.

Page 12: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AbouttheReviewerElmerThomascompletedaB.S.inComputerEngineeringandaM.S.inElectricalEngineeringattheUniversityofCalifornia,Riverside.HisfocuswasonControlSystems,specificallyGPSnavigationsystems,spendingseveralyearsservingasaresearchassistant,buildingsoftwareandhardwareforselfdrivingcarsatU.C.RiversideandBerkeley,resultingin2co-publications:AidedIntegerAmbiguityResolutionAlgorithmandDataFusionviaKalmanFilter:GPS&INS.DuringthefinalyearsofhisMastersprogram,headdedafewmentors,partnersandsomebusinessskillsthroughtheTuckExecutiveProgramatDartmouthtohisrepertoireandco-foundedseveralcompanieswithvaryingdegreesofsuccessoverthenext7years.Duringthistimehehelpedhundredsofbusinessprofitwhileachievingover50awardsfromlocalandstategovernmentforserviceinthecommunity.

Whilebuildingbusinesses,ElmerservedonvariousboardstohelpfostergrowthinlocalbusinesscommunitiesinRiversideandOrangeCounty,includingtheRiversideTechnologyCEOForum,theTechBizConnection,OCTANeandTriTech.Next,hebeganservingatSendGrid,anemailAPIandServiceCompany,asoneofthefirst5employeesinanow300+employeecompanyonthevergeofgoingpublic.Servicebeganasthewebdevelopmentmanager,andthenhemovedintoaproductdevelopmentrolewhilehelpingbuildoutaqualityassuranceprogram.Afterspending2yearstravelingtoover50events,speaking,teachingandmentoringasaDeveloperEvangelistwithintheSendGridmarketingdepartment,ElmerthenservedastheHackerinResidenceonthecommunityteamatSendGrid.Inthatrolehementoredover50startups,manybelongingtoacceleratorssuchasTechstarsand500Startups,andhundredsofdevelopersthroughliveconsultinganddevelopmentofproductivitycontentandsoftware.

HecurrentlyservesastheDeveloperExperienceEngineeratSendGrid,leading,developingandmanagingSendGrid’sopensourcecommunity,whichincludesover24activeprojectsacross7programminglanguages.Theseopensourceprojectsprocesshundredsofmillionsofemailsperdayforourcustomers.HealsoservesasVicePresidentoftheCouncilfortheAdvancementofBlackEngineers,drawingfromexperienceaschapterpresidentoftheNationalSocietyofBlackEngineerswhileastudentatU.C.Riverside,supportingourmissiontoincreasethenumberofculturallyresponsibleBlackEngineerswithPhD’s,post-doctoraltrainingandprofessionalengineeringregistrations.

AsmemberoftheboardofdirectorsforOperationCode,hehelpsequipmilitaryveteransandtheirfamilieswithprogrammingknowledgethroughmentorshiptohelpveteranscreatenewcareerpathsinsoftwaredevelopment.ThroughhisvolunteerworkwiththeGirlsScoutsofSanGorgonioCouncil,ElmerfocusesonhelpingbringSTEMexperiencestogirls,specificallywithintheagegroupsbetween9and14yearsold,includinghisown11yearolddaughter,whoisnowaGirlScoutcadette.Tohelpservehislocalcommunity,heisamemberoftheboardofdirectorsofhislocalHOA.Heisconsideredasocialmediainfluencer,driving100sofmillionsofvisitstovariouswebpages.HeisknownasThinkingSeriousonvarioussocialnetworks.

Page 13: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Elmer'spassionsincludefamilytimewithhiswife,and2daughters,reading,writing,watchingvideos,especiallyinvirtualreality,developingsoftwareandcreatingingeneral,especiallyintheareaofpersonaldevelopmentandproductivitythroughquantificationtechniques.IwouldliketothankmywifeLindaanddaughterAudreyfortheirpatienceandquiettimeformetocompletethisreview.

Moredetailcanbefoundathisblog,ThinkingSerious.com.

Page 14: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www.packtpub.com/mapt

Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.

Page 15: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 16: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

PrefaceREST (RepresentationalStateTransfer)isthearchitecturalstylethatisdrivingmodernwebdevelopmentandmobileapps.Infact,developingandinteractingwithRESTfulWebServicesisarequiredskillinanymodernsoftwaredevelopmentjob.Sometimes,youhavetointeractwithanexistingAPIandinothercases,youhavetodesignaRESTfulAPIfromscratchandmakeitworkwithJSON(JavaScriptObjectNotation).

Pythonisoneofthemostpopularprogramminglanguages.Python3.5isthemostmodernversionofPython.Itisopensource,multiplatform,andyoucanuseittodevelopanykindofapplication,fromwebsitestoextremelycomplexscientificcomputingapplications.ThereisalwaysaPythonpackagethatmakesthingseasierforyoutoavoidreinventingthewheelandsolvetheproblemsfaster.ThemostimportantandpopularCloudcomputingprovidersmakeiteasytoworkwithPythonanditsrelatedWebframeworks.Thus,PythonisanidealchoicefordevelopingRESTfulWebServices.ThebookcoversallthethingsyouneedtoknowtoselectthemostappropriatePythonWebframeworkanddevelopaRESTfulAPIfromscratch.

YouwillworkwiththethreemostpopularPythonwebframeworksthatmakeiteasytodevelopRESTfulWebServices:Django,Flask,andTornado.Eachwebframeworkhasitsadvantagesandtradeoffs.YouwillworkwithexamplesthatrepresentappropriatecasesforeachoftheseWebframeworks,incombinationwithadditionalPythonpackagesthatwillsimplifythemostcommontasks.Youwilllearntousedifferenttoolstotestanddevelophigh-quality,consistentandscalableRESTfulWebServices.Youwillalsotakeadvantageofobject-orientedprogramming,alsoknownasOOP,tomaximizecodereuseandminimizemaintenancecosts.

YouwillalwayswriteunittestsandimprovetestcoverageforalloftheRESTfulWebServicesthatyouwilldevelopthroughoutthebook.Youwon’tjustrunthesamplecodebutyouwillalsomakesurethatyouwritetestsforyourRESTfulAPI.

ThisbookwillallowyoutolearnhowtotakeadvantageofmanypackagesthatwillsimplifythemostcommontasksrelatedtoRESTfulWebServices.YouwillbeabletostartcreatingyourownRESTfulAPIsforanydomaininanyofthecoveredWebframeworksinPython3.5orgreater.

Page 17: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WhatthisbookcoversChapter1,DevelopingRESTfulAPIswithDjango,inthischapterwewillstartworkingwithDjangoandDjangoRESTFramework,andwewillcreateaRESTfulWebAPIthatperformsCRUD(Create,Read,UpdateandDelete)operationsonasimpleSQLitedatabase.

Chapter2,WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjango,inthischapterwewillexpandthecapabilitiesoftheRESTfulAPIthatwestartedinthepreviouschapter.WewillchangetheORMsettingstoworkwithamorepowerfulPostgreSQLdatabaseandwewilltakeadvantageofadvancedfeaturesincludedinDjangoRESTFrameworkthatallowustoreduceboilerplatecodeforcomplexAPIs,suchasclassbasedviews.

Chapter3,ImprovingandAddingAuthenticationtoanAPIwithDjango,inthischapterwewillimprovetheRESTfulAPIthatwestartedinthepreviouschapter.Wewilladduniqueconstraintstothemodelandupdatethedatabase.WewillmakeiteasytoupdatesinglefieldswiththePATCHmethodandwewilltakeadvantageofpagination.Wewillstartworkingwithauthentication,permissionsandthrottling.

Chapter4,Throttling,Filtering,TestingandDeployinganAPIwithDjango,inthischapterwewilltakeadvantageofmanyfeaturesincludedinDjangoRESTFrameworktodefinethrottlingpolicies.Wewillusefiltering,searchingandorderingclassestomakeiteasytoconfigurefilters,searchqueriesanddesiredorderfortheresultsinHTTPrequests.WewillusethebrowsableAPIfeaturetotestthesenewfeaturesincludedinourAPI.Wewillwriteafirstroundofunittests,measuretestcoverageandthenwriteadditionalunitteststoimprovetestcoverage.Finally,wewilllearnmanyconsiderationsfordeploymentandscalability.

Chapter5,DevelopingRESTfulAPIswithFlask,inthischapterwewillstartworkingwithFlaskanditsFlask-RESTfulextension.WewillcreateaRESTfulWebAPIthatperformsCRUDoperationsonasimplelist.

Chapter6,WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlask,inthischapterwewillexpandthecapabilitiesoftheRESTfulAPIthatwestartedinthepreviouschapter.WewilluseSQLAlchemyasourORMtoworkwithaPostgreSQLdatabaseandwewilltakeadvantageofadvancedfeaturesincludedinFlaskandFlask-RESTfulthatwillallowustoeasilyorganizecodeforcomplexAPIs,suchasmodelsandblueprints.

Chapter7,ImprovingandAddingAuthenticationtoanAPIwithFlask,inthischapterwewillimprovetheRESTfulAPIinmanyways.Wewilladduserfriendlyerrormessageswhenresourcesaren’tunique.WewilltesthowtoupdatesingleormultiplefieldswiththePATCHmethodandwewillcreateourowngenericpaginationclass.Then,wewillstartworkingwithauthenticationandpermissions.Wewilladdedausermodelandwewillupdatethedatabase.WewillmakemanychangesinthedifferentpiecesofcodetoachieveaspecificsecuritygoalandwewilltakeadvantageofFlask-HTTPAuthandpasslibtouseHTTPauthenticationinourAPI.

Page 18: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter8,TestingandDeployinganAPIwithFlask,inthischapterwewillsetupatestingenvironment.Wewillinstallnose2tomakeiteasytodiscoverandexecuteunittestsandwewillcreateanewdatabasetobeusedfortesting.Wewillwriteafirstroundofunittests,measuretestcoverageandthenwriteadditionalunitteststoimprovetestcoverage.Finally,wewilllearnmanyconsiderationsfordeploymentandscalability.

Chapter9,DevelopingRESTfulAPIswithTornado,wewillworkwithTornadotocreateaRESTfulWebAPI.WewilldesignaRESTfulAPItointeractwithslowsensorsandactuators.WewilldefinedtherequirementsforourAPIandwewillunderstandthetasksperformedbyeachHTTPmethod.WewillcreatetheclassesthatrepresentadroneandwritecodetosimulateslowI/OoperationsthatarecalledforeachHTTPrequestmethod.WewillwriteclassesthatrepresentrequesthandlersandprocessthedifferentHTTPrequestsandconfiguretheURLpatternstorouteURLstorequesthandlersandtheirmethods.

Chapter10,WorkingwithAsynchronousCode,Testing,andDeployinganAPIwithTornado,inthischapterwewillunderstandthedifferencebetweensynchronousandasynchronousexecution.WewillcreateanewversionoftheRESTfulAPIthattakesadvantageofthenon-blockingfeaturesinTornadocombinedwithasynchronousexecution.WewillimprovescalabilityforourexistingAPIandwewillmakeitpossibletostartexecutingotherrequestswhilewaitingfortheslowI/Ooperationswithsensorsandactuators.Then,wewillsetupatestingenvironment.Wewillinstallnose2tomakeiteasytodiscoverandexecuteunittests.Wewillwroteafirstroundofunittests,measuretestcoverageandthenwriteadditionalunitteststoimprovetestcoverage.Wewillcreateallthenecessaryteststohaveacompletecoverageofallthelinesofcode.

Page 19: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WhatyouneedforthisbookInordertoworkwiththedifferentsamplesforPython3.5.x,youwillneedanycomputerwithanIntelCorei3orhigherCPUandatleast4GBRAM.Youcanworkwithanyofthefollowingoperatingsystems:

Windows7orgreater(Windows8,Windows8.1orWindows10)macOSMountainLionorgreaterAnyLinuxversioncapableofrunningPython3.5.xandanymodernbrowserwithJavaScriptsupport

YouwillneedPython3.5orgreaterinstalledonyourcomputer.

Page 20: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WhothisbookisforThisbookisforwebdeveloperswhohaveworkingknowledgeofPythonandwouldliketobuildamazingwebservicesbytakingadvantageofthevariousframeworksofPython.YoushouldhavesomeknowledgeofRESTfulAPIs.

Page 21: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"Ifnogamematchesthespecifiedidorprimarykey,theserverwillreturnjusta404NotFoundstatus."

Ablockofcodeissetasfollows:

fromdjango.appsimportAppConfig

classGamesConfig(AppConfig):

name='games'

Anycommand-lineinputoroutputiswrittenasfollows:

python3-mvenv~/PythonREST/Django01

Note

Warningsorimportantnotesappearinaboxlikethis.

Tip

Tipsandtricksappearlikethis.

Page 22: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 23: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 24: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORT tabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux

ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Building-RESTful-Python-Web-Services.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 25: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks-maybeamistakeinthetextorthecode-wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

Page 26: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 27: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.

Page 28: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter1.DevelopingRESTfulAPIswithDjangoInthischapter,wewillstartourjourneytowardsRESTfulWebAPIswithPythonandfourdifferentWebframeworks.Pythonisoneofthemostpopularandversatileprogramminglanguages.TherearethousandsofPythonpackages,whichallowyoutoextendPythoncapabilitiestoanykindofdomainyoucanimagine.WecanworkwithmanydifferentWebframeworksandpackagestoeasilybuildsimpleandcomplexRESTfulWebAPIswithPython,andwecanalsocombinetheseframeworkswithotherPythonpackages.

WecanleverageourexistingknowledgeofPythonanditspackagestocodethedifferentpiecesofourRESTfulWebAPIsandtheirecosystem.Wecanusetheobject-orientedfeaturestocreatecodethatiseasiertomaintain,understand,andreuse.Wecanuseallthepackagesthatwealreadyknowtointeractwithdatabases,Webservices,anddifferentAPIs.PythonmakesiteasyforustocreateRESTfulWebAPIs.Wedon'tneedtolearnanotherprogramminglanguage;wecanusetheonewealreadyknowandlove.

Inthischapter,wewillstartworkingwithDjangoandDjangoRESTFramework,andwewillcreateaRESTfulWebAPIthatperformsCRUD(Create,Read,Update,andDelete)operationsonasimpleSQLitedatabase.Wewill:

DesignaRESTfulAPItointeractwithasimpleSQLitedatabaseUnderstandthetasksperformedbyeachHTTPmethodSetupthevirtualenvironmentwithDjangoRESTframeworkCreatethedatabasemodelsManageserializationanddeserializationofdataWriteAPIviewsMakeHTTPrequeststotheAPIwithcommand-linetoolsWorkwithGUItoolstocomposeandsendHTTPrequests

Page 29: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DesigningaRESTfulAPItointeractwithasimpleSQLitedatabaseImaginethatwehavetostartworkingonamobileAppthathastointeractwithaRESTfulAPItoperformCRUDoperationswithgames.Wedon'twanttospendtimechoosingandconfiguringthemostappropriateORM(Object-RelationalMapping);wejustwanttofinishtheRESTfulAPIassoonaspossibletostartinteractingwithitviaourmobileApp.Wereallywantthegamestopersistinadatabasebutwedon'tneedittobeproduction-ready,andtherefore,wecanusethesimplestpossiblerelationaldatabase,aslongaswedon'thavetospendtimemakingcomplexinstallationsorconfigurations.

DjangoRESTframework,alsoknownasDRF,willallowustoeasilyaccomplishthistaskandstartmakingHTTPrequeststoourfirstversionofourRESTfulWebService.Inthiscase,wewillworkwithaverysimpleSQLitedatabase,thedefaultdatabaseforanewDjangoRESTframeworkproject.

First,wemustspecifytherequirementsforourmainresource:agame.Weneedthefollowingattributesorfieldsforagame:

AnintegeridentifierAnameortitleAreleasedateAgamecategorydescription,suchas3DRPGand2Dmobilearcade.Aboolvalueindicatingwhetherthegamewasplayedatleastoncebyaplayerornot

Inaddition,wewantourdatabasetosaveatimestampwiththedateandtimeinwhichthegamewasinsertedinthedatabase.

ThefollowingtableshowstheHTTPverbs,thescope,andthesemanticsforthemethodsthatourfirstversionoftheAPImustsupport.EachmethodiscomposedbyanHTTPverbandascopeandallthemethodshaveawelldefinedmeaningforallgamesandcollections.

HTTPverb Scope Semantics

GETCollectionofgames

Retrieveallthestoredgamesinthecollection,sortedbytheirnameinascendingorder

GET Game Retrieveasinglegame

Collectionof

Page 30: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

POST games Createanewgameinthecollection

PUT Game Updateanexistinggame

DELETE Game Deleteanexistinggame

Tip

InaRESTfulAPI,eachresourcehasitsownuniqueURL.InourAPI,eachgamehasitsownuniqueURL.

Page 31: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthetasksperformedbyeachHTTPmethodIntheprecedingtable,theGETHTTPverbappearstwicebutwithtwodifferentscopes.ThefirstrowshowsaGETHTTPverbappliedtoacollectionofgames(collectionofresources)andthesecondrowshowsaGETHTTPverbappliedtoagame(asingleresource).

Let'sconsiderthathttp://localhost:8000/games/istheURLforthecollectionofgames.Ifweaddanumberandaslash(/)totheprecedingURL,weidentifyaspecificgamewhoseidorprimarykeyisequaltothespecifiednumericvalue.Forexample,http://localhost:8000/games/12/identifiesthegamewhoseidorprimarykeyisequalto12.

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(POST)andrequestURL(http://localhost:8000/games/)tocreateanewgame.Inaddition,wehavetoprovidetheJSON(JavaScriptObjectNotation)key-valuepairswiththefieldnamesandthevaluestocreatethenewgame.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefields,makesurethatitisavalidgameandpersistitinthedatabase.

Theserverwillinsertanewrowwiththenewgameintheappropriatetableanditwillreturna201CreatedstatuscodeandaJSONbodywiththerecentlyaddedgameserializedtoJSON,includingtheassignedidorprimarykeythatwasautomaticallygeneratedbythedatabaseandassignedtothegameobject.

POSThttp://localhost:8000/games/

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:8000/games/{id}/)toretrievethegamewhoseidorprimarykeymatchesthespecifiednumericvalueintheplacewhere{id}iswritten.

Forexample,ifweusetherequestURLhttp://localhost:8000/games/50/,theserverwillretrievethegamewhoseidorprimarykeymatches50.

Asaresultoftherequest,theserverwillretrieveagamewiththespecifiedidorprimarykeyfromthedatabaseandcreatetheappropriategameobjectinPython.Ifagameisfound,theserverwillserializethegameobjectintoJSONandreturna200OKstatuscodeandaJSONbodywiththeserializedgameobject.Ifnogamematchesthespecifiedidorprimarykey,theserverwillreturnjusta404NotFoundstatus:

GEThttp://localhost:8000/games/{id}/

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(PUT)andrequestURL(http://localhost:8000/games/{id}/)toretrievethegamewhoseidorprimarykeymatchesthespecifiednumericvalueintheplacewhere{id}iswrittenand

Page 32: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

replaceitwithagamecreatedwiththeprovideddata.Inaddition,wehavetoprovidetheJSONkey-valuepairswiththefieldnamesandthevaluestocreatethenewgamethatwillreplacetheexistingone.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefields,makesurethatitisavalidgameandreplacetheonethatmatchesthespecifiedidorprimarykeywiththenewoneinthedatabase.Theidorprimarykeyforthegamewillbethesameaftertheupdateoperation.Theserverwillupdatetheexistingrowintheappropriatetableanditwillreturna200OKstatuscodeandaJSONbodywiththerecentlyupdatedgameserializedtoJSON.Ifwedon'tprovideallthenecessarydataforthenewgame,theserverwillreturna400BadRequeststatuscode.Iftheserverdoesn'tfindagamewiththespecifiedid,theserverwillreturnjusta404NotFoundstatus.

PUThttp://localhost:8000/games/{id}/

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(DELETE)andrequestURL(http://localhost:8000/games/{id}/)toremovethegamewhoseidorprimarykeymatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Forexample,ifweusetherequestURLhttp://localhost:8000/games/20/,theserverwilldeletethegamewhoseidorprimarykeymatches20.Asaresultoftherequest,theserverwillretrieveagamewiththespecifiedidorprimarykeyfromthedatabaseandcreatetheappropriategameobjectinPython.Ifagameisfound,theserverwillrequesttheORMtodeletethegamerowassociatedwiththisgameobjectandtheserverwillreturna204NoContentstatuscode.Ifnogamematchesthespecifiedidorprimarykey,theserverwillreturnjusta404NotFoundstatus.

DELETEhttp://localhost:8000/games/{id}/

Page 33: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithlightweightvirtualenvironmentsThroughoutthisbook,wewillbeworkingwithdifferentframeworksandlibraries,andtherefore,itisconvenienttoworkwithvirtualenvironments.WewillworkwiththelightweightvirtualenvironmentsintroducedinPython3.3andimprovedinPython3.4.However,youcanalsochoosetousethepopularvirtualenv(https://pypi.python.org/pypi/virtualenv)third-partyvirtualenvironmentbuilderorthevirtualenvironmentoptionsprovidedbyyourPythonIDE.

Youjusthavetomakesurethatyouactivateyourvirtualenvironmentwiththeappropriatemechanismwhenitisnecessarytodoso,insteadoffollowingthestepexplainedtoactivatethevirtualenvironmentgeneratedwiththevenvmoduleintegratedinPython.YoucanreadmoreinformationaboutPEP405PythonVirtualEnvironmentthatintroducedthevenvmoduleathttps://www.python.org/dev/peps/pep-0405.

Tip

EachvirtualenvironmentwecreatewithvenvisanisolatedenvironmentanditwillhaveitsownindependentsetofinstalledPythonpackagesinitssitedirectories.WhenwecreateavirtualenvironmentwithvenvinPython3.4andgreater,pipisincludedinthenewvirtualenvironment.InPython3.3,itwasnecessarytomanuallyinstallpipaftercreatingthevirtualenvironment.NoticethattheinstructionsprovidedarecompatiblewithPython3.4orgreater,includingPython3.5.x.ThefollowingcommandsassumethatyouhavePython3.5.xinstalledonmacOS,Linux,orWindows.

First,wehavetoselectthetargetfolderordirectoryforourvirtualenvironment.ThefollowingisthepathwewilluseintheexampleformacOSandLinux.ThetargetfolderforthevirtualenvironmentwillbethePythonREST/Djangofolderwithinourhomedirectory.Forexample,ifourhomedirectoryinmacOSorLinuxis/Users/gaston,thevirtualenvironmentwillbecreatedwithin/Users/gaston/PythonREST/Django.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand.

~/PythonREST/Django

ThefollowingisthepathwewilluseintheexampleforWindows.ThetargetfolderforthevirtualenvironmentwillbethePythonREST/Djangofolderwithinouruserprofilefolder.Forexample,ifouruserprofilefolderisC:\Users\Gaston,thevirtualenvironmentwillbecreatedwithinC:\Users\gaston\PythonREST\Django.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand.

%USERPROFILE%\PythonREST\Django

Now,wehavetousethe-moptionfollowedbythevenvmodulenameandthedesiredpathtomakePythonrunthismoduleasascriptandcreateavirtualenvironmentinthespecifiedpath.Theinstructionsaredifferentdependingontheplatforminwhichwearecreatingthevirtual

Page 34: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

environment.

OpenaTerminalinmacOSorLinuxandexecutethefollowingcommandtocreateavirtualenvironment:

python3-mvenv~/PythonREST/Django01

InWindows,executethefollowingcommandtocreateavirtualenvironment:

python-mvenv%USERPROFILE%\PythonREST\Django01

Theprecedingcommanddoesn'tproduceanyoutput.Thescriptcreatedthespecifiedtargetfolderandinstalledpipbyinvokingensurepipbecausewedidn'tspecifythe--without-pipoption.ThespecifiedtargetfolderhasanewdirectorytreethatcontainsPythonexecutablefilesandotherfilesthatindicatethatitisavirtualenvironment.

Thepyenv.cfgconfigurationfilespecifiesdifferentoptionsforthevirtualenvironmentanditsexistenceisanindicatorthatweareintherootfolderforavirtualenvironment.InOSandLinux,thefolderwillhavethefollowingmainsub-folders—bin,include,lib,lib/python3.5andlib/python3.5/site-packages.InWindows,thefolderwillhavethefollowingmainsub-folders—Include,Lib,Lib\site-packages,andScripts.ThedirectorytreesforthevirtualenvironmentineachplatformarethesameasthelayoutofthePythoninstallationintheseplatforms.ThefollowingscreenshotshowsthefoldersandfilesinthedirectorytreesgeneratedfortheDjango01virtualenvironmentinmacOS:

Page 35: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thefollowingscreenshotshowsthemainfoldersinthedirectorytreesgeneratedforthe

Page 36: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

virtualenvironmentsinWindows:

Tip

Afterweactivatethevirtualenvironment,wewillinstallthird-partypackagesintothevirtualenvironmentandthemoduleswillbelocatedwithinthelib/python3.5/site-packagesorLib\site-packagesfolder,basedontheplatform.TheexecutableswillbecopiedinthebinorScriptsfolder,basedontheplatform.Thepackagesweinstallwon'tmakechangestoothervirtualenvironmentsorourbasePythonenvironment.

Nowthatwehavecreatedavirtualenvironment,wewillrunaplatform-specificscripttoactivateit.Afterweactivatethevirtualenvironment,wewillinstallpackagesthatwillonlybeavailableinthisvirtualenvironment.

RunthefollowingcommandintheterminalinmacOSorLinux.Notethattheresultsofthiscommandwillbeaccurateifyoudon'tstartadifferentshellthanthedefaultshellintheterminalsession.Incaseyouhavedoubts,checkyourterminalconfigurationandpreferences.

echo$SHELL

ThecommandwilldisplaythenameoftheshellyouareusingintheTerminal.InmacOS,thedefaultis/bin/bashandthismeansyouareworkingwiththebashshell.Dependingontheshell,youmustrunadifferentcommandtoactivatethevirtualenvironmentinOSorLinux.

Page 37: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

IfyourTerminalisconfiguredtousethebashshellinmacOSorLinux,runthefollowingcommandtoactivatethevirtualenvironment.Thecommandalsoworksforthezshshell:

source~/PythonREST/Django01/bin/activate

IfyourTerminalisconfiguredtouseeitherthecshortcshshell,runthefollowingcommandtoactivatethevirtualenvironment:

source~/PythonREST/Django01/bin/activate.csh

IfyourTerminalisconfiguredtouseeitherthefishshell,runthefollowingcommandtoactivatethevirtualenvironment:

source~/PythonREST/Django01/bin/activate.fish

InWindows,youcanruneitherabatchfileinthecommandpromptoraWindowsPowerShellscripttoactivatethevirtualenvironment.Ifyoupreferthecommandprompt,runthefollowingcommandintheWindowscommandlinetoactivatethevirtualenvironment:

%USERPROFILE%\PythonREST\Django01\Scripts\activate.bat

IfyouprefertheWindowsPowerShell,launchitandrunthefollowingcommandstoactivatethevirtualenvironment.However,noticethatyoushouldhavescriptsexecutionenabledinWindowsPowerShelltobeabletorunthescript:

cd$env:USERPROFILE

PythonREST\Django01\Scripts\Activate.ps1

Afteryouactivatethevirtualenvironment,thecommandpromptwilldisplaythevirtualenvironmentrootfoldernameenclosedinparenthesisasaprefixofthedefaultprompttoremindusthatweareworkinginthevirtualenvironment.Inthiscase,wewillsee(Django01)asaprefixforthecommandpromptbecausetherootfolderfortheactivatedvirtualenvironmentisDjango01.

ThefollowingscreenshotshowsthevirtualenvironmentactivatedinamacOSElCapitanterminalwithabashshell,afterexecutingthepreviouslyshowncommands:

Page 38: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Aswecanseeintheprecedingscreenshot,thepromptchangedfromGastons-MacBook-Pro:~gaston$to(Django01)Gastons-MacBook-Pro:~gaston$aftertheactivationofthevirtualenvironment.

ThefollowingscreenshotshowsthevirtualenvironmentactivatedinaWindows10CommandPrompt,afterexecutingthepreviouslyshowncommands:

Aswecannoticefromtheprecedingscreenshot,thepromptchangedfromC:\Users\gaston\AppData\Local\Programs\Python\Python35to(Django01)C:\Users\gaston\AppData\Local\Programs\Python\Python35aftertheactivationofthevirtualenvironment.

Tip

Itisextremelyeasytodeactivateavirtualenvironmentgeneratedwiththepreviouslyexplainedprocess.InmacOSorLinux,justtypedeactivateandpressEnter.InaWindowscommandprompt,youhavetorunthedeactivate.batbatchfileincludedintheScriptsfolder(%USERPROFILE%\PythonREST\Django01\Scripts\deactivate.batinourexample).InWindowsPowerShell,youhavetoruntheDeactivate.ps1scriptintheScriptsfolder.Thedeactivationwillremoveallthechangesmadeintheenvironmentvariables.

Page 39: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupthevirtualenvironmentwithDjangoRESTframeworkWehavecreatedandactivatedavirtualenvironment.ItistimetorunmanycommandsthatwillbethesameforeithermacOS,LinuxorWindows.Now,wemustrunthefollowingcommandtoinstalltheDjangoWebframework:

pipinstalldjango

Thelastlinesoftheoutputwillindicatethatthedjangopackagehasbeensuccessfullyinstalled.Takeintoaccountthatyoumayalsoseeanoticetoupgradepip.

Collectingdjango

Installingcollectedpackages:django

Successfullyinstalleddjango-1.10

NowthatwehaveinstalledDjangoWebframework,wecaninstallDjangoRESTframework.Wejustneedtorunthefollowingcommandtoinstallthispackage:

pipinstalldjangorestframework

Thelastlinesfortheoutputwillindicatethatthedjangorestframeworkpackagehasbeensuccessfullyinstalled:

Collectingdjangorestframework

Installingcollectedpackages:djangorestframework

Successfullyinstalleddjangorestframework-3.3.3

Gototherootfolderforthevirtualenvironment-Django01.InmacOSorLinux,enterthefollowingcommand:

cd~/PythonREST/Django01

InWindows,enterthefollowingcommand:

cd/d%USERPROFILE%\PythonREST\Django01

RunthefollowingcommandtocreateanewDjangoprojectnamedgamesapi.Thecommandwon'tproduceanyoutput:

django-admin.pystartprojectgamesapi

Thepreviouscommandcreatedagamesapifolderwithothersub-foldersandPythonfiles.Now,gototherecentlycreatedgamesapifolder.Justexecutethefollowingcommand:

cdgamesapi

Then,runthefollowingcommandtocreateanewDjangoappnamedgameswithinthe

Page 40: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

gamesapiDjangoproject.Thecommandwon'tproduceanyoutput:

pythonmanage.pystartappgames

Thepreviouscommandcreatedanewgamesapi/gamessub-folder,withthefollowingfiles:

__init__.py

admin.py

apps.py

models.py

tests.py

views.py

Inaddition,thegamesapi/gamesfolderwillhaveamigrationssub-folderwithan__init__.pyPythonscript.Thefollowingdiagramshowsthefoldersandfilesinthedirectorytreesstartingatthegamesapifolder:

Page 41: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Let'scheckthePythoncodeintheapps.pyfilewithinthegamesapi/gamesfolder.The

Page 42: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

followinglinesshowsthecodeforthisfile:

fromdjango.appsimportAppConfig

classGamesConfig(AppConfig):

name='games'

ThecodedeclarestheGamesConfigclassasasubclassofthedjango.apps.AppConfigclassthatrepresentsaDjangoapplicationanditsconfiguration.TheGamesConfigclassjustdefinesthenameclassattributeandsetsitsvalueto'games'.Wehavetoaddgames.apps.GamesConfigasoneoftheinstalledappsinthegamesapi/settings.pyfilethatconfiguressettingsforthegamesapiDjangoproject.Webuilttheprecedingstringasfollows-appname+.apps.+classname,whichis,games+.apps.+GamesConfig.Inaddition,wehavetoaddtherest_frameworkapptomakeitpossibleforustouseDjangoRESTFramework.

Thegamesapi/settings.pyfileisaPythonmodulewithmodule-levelvariablesthatdefinetheconfigurationofDjangoforthegamesapiproject.WewillmakesomechangestothisDjangosettingsfile.Openthegamesapi/settings.pyfileandlocatethefollowinglinesthatspecifythestringslistthatdeclarestheinstalledapps:

INSTALLED_APPS=[

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

]

AddthefollowingtwostringstotheINSTALLED_APPSstringslistandsavethechangestothegamesapi/settings.pyfile:

'rest_framework'

'games.apps.GamesConfig'

ThefollowinglinesshowthenewcodethatdeclarestheINSTALLED_APPSstringslistwiththeaddedlineshighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

INSTALLED_APPS=[

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

#DjangoRESTFramework

'rest_framework',

#Gamesapplication

'games.apps.GamesConfig',

Page 43: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

]

Thisway,wehaveaddedDjangoRESTFrameworkandthegamesapplicationtoourinitialDjangoprojectnamedgamesapi.

Page 44: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingthemodelsNow,wewillcreateasimpleGamemodelthatwewillusetorepresentandpersistgames.Openthegames/models.pyfile.Thefollowinglinesshowtheinitialcodeforthisfile,withjustoneimportstatementandacommentthatindicatesweshouldcreatethemodels:

fromdjango.dbimportmodels

#Createyourmodelshere.

ThefollowinglinesshowthenewcodethatcreatesaGameclass,specifically,aGamemodelinthegames/models.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

fromdjango.dbimportmodels

classGame(models.Model):

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=200,blank=True,default='')

release_date=models.DateTimeField()

game_category=models.CharField(max_length=200,blank=True,default='')

played=models.BooleanField(default=False)

classMeta:

ordering=('name',)

TheGameclassisasubclassofthedjango.db.models.Modelclass.Eachdefinedattributerepresentsadatabasecolumnorfield.Djangoautomaticallyaddsanauto-incrementintegerprimarykeycolumnnamedidwhenitcreatesthedatabasetablerelatedtothemodel.However,themodelmapstheunderlyingidcolumninanattributenamedpkforthemodel.Wespecifiedthefieldtypes,maximumlengthsanddefaultsformanyattributes.TheclassdeclaresaMetainnerclassthatdeclaresaorderingattributeandsetsitsvaluetoatupleofstringwhosefirstvalueisthe'name'string,indicatingthat,bydefault,wewanttheresultsorderedbythenameattributeinascendingorder.

Then,itisnecessarytocreatetheinitialmigrationforthenewGamemodelwerecentlycoded.WejustneedtorunthefollowingPythonscriptsandwewillalsosynchronizethedatabaseforthefirsttime.Bydefault,DjangousesanSQLitedatabase.Inthisexample,wewillbeworkingwiththisdefaultconfiguration:

pythonmanage.pymakemigrationsgames

Thefollowinglinesshowtheoutputgeneratedafterrunningtheprecedingcommand.

Migrationsfor'games':

0001_initial.py:

-CreatemodelGame

Page 45: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Theoutputindicatesthatthegamesapi/games/migrations/0001_initial.pyfileincludesthecodetocreatetheGamemodel.ThefollowinglinesshowthecodeforthisfilethatwasautomaticallygeneratedbyDjango.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

#-*-coding:utf-8-*-

#GeneratedbyDjango1.9.6on2016-05-1721:19

from__future__importunicode_literals

fromdjango.dbimportmigrations,models

classMigration(migrations.Migration):

initial=True

dependencies=[

]

operations=[

migrations.CreateModel(

name='Game',

fields=[

('id',models.AutoField(auto_created=True,primary_key=True,

serialize=False,verbose_name='ID')),

('created',models.DateTimeField(auto_now_add=True)),

('name',models.CharField(blank=True,default='',

max_length=200)),

('release_date',models.DateTimeField()),

('game_category',models.CharField(blank=True,default='',

max_length=200)),

('played',models.BooleanField(default=False)),

],

options={

'ordering':('name',),

},

),

]

Thecodedefinesasubclassofthedjango.db.migrations.MigrationclassnamedMigrationthatdefinesanoperationthatcreatestheGamemodel'stable.Now,runthefollowingpythonscripttoapplyallthegeneratedmigrations:

pythonmanage.pymigrate

Thefollowinglinesshowtheoutputgeneratedafterrunningtheprecedingcommand:

Operationstoperform:

Applyallmigrations:sessions,games,contenttypes,admin,auth

Runningmigrations:

Renderingmodelstates...DONE

Applyingcontenttypes.0001_initial...OK

Applyingauth.0001_initial...OK

Applyingadmin.0001_initial...OK

Page 46: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Applyingadmin.0002_logentry_remove_auto_add...OK

Applyingcontenttypes.0002_remove_content_type_name...OK

Applyingauth.0002_alter_permission_name_max_length...OK

Applyingauth.0003_alter_user_email_max_length...OK

Applyingauth.0004_alter_user_username_opts...OK

Applyingauth.0005_alter_user_last_login_null...OK

Applyingauth.0006_require_contenttypes_0002...OK

Applyingauth.0007_alter_validators_add_error_messages...OK

Applyinggames.0001_initial...OK

Applyingsessions.0001_initial...OK

Afterweruntheprecedingcommand,wewillnoticethattherootfolderforourgamesapiprojectnowhasadb.sqlite3file.WecanusetheSQLitecommandlineoranyotherapplicationthatallowsustoeasilycheckthecontentsoftheSQLitedatabasetocheckthetablesthatDjangogenerated.

InmacOSandmostmodernLinuxdistributions,SQLiteisalreadyinstalled,andtherefore,youcanrunthesqlite3command-lineutility.However,inWindows,ifyouwanttoworkwiththesqlite3.execommand-lineutility,youwillhavetodownloadandinstallSQLitefromitsWebpage-http://www.sqlite.org.

Runthefollowingcommandtolistthegeneratedtables:

sqlite3db.sqlite3'.tables'

RunthefollowingcommandtoretrievetheSQLusedtocreatethegames_gametable:

sqlite3db.sqlite3'.schemagames_game'

Thefollowingcommandwillallowyoutocheckthecontentsofthegames_gametableafterwecomposeandsendHTTPrequeststotheRESTfulAPIandmakeCRUDoperationstothegames_gametable:

sqlite3db.sqlite3'SELECT*FROMgames_gameORDERBYname;'

InsteadofworkingwiththeSQLitecommand-lineutility,youcanuseaGUItooltocheckthecontentsoftheSQLitedatabase.DBBrowserforSQLiteisausefulmultiplatformandfreeGUItoolthatallowsustoeasilycheckthedatabasecontentsofanSQLitedatabaseinmacOS,LinuxandWindows.Youcanreadmoreinformationaboutthistoolanddownloaditsdifferentversionsfromhttp://sqlitebrowser.org.Onceyouinstalledthetool,youjustneedtoopenthedb.sqlite3fileandyoucancheckthedatabasestructureandbrowsethedataforthedifferenttables.YoucanusealsothedatabasetoolsincludedinyourfavoriteIDEtocheckthecontentsfortheSQLitedatabase.

TheSQLitedatabaseengineandthedatabasefilenamearespecifiedinthegamesapi/settings.pyPythonfile.ThefollowinglinesshowthedeclarationoftheDATABASESdictionarythatcontainsthesettingsforallthedatabasethatDjangouses.Thenesteddictionarymapsthedatabasenameddefaultwiththedjango.db.backends.sqlite3databaseengineandthedb.sqlite3databasefilelocatedintheBASE_DIRfolder(gamesapi):

Page 47: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DATABASES={

'default':{

'ENGINE':'django.db.backends.sqlite3',

'NAME':os.path.join(BASE_DIR,'db.sqlite3'),

}

}

Afterweexecutedthemigrations,theSQLitedatabasewillhavethefollowingtables:

auth_group

auth_group_permissions

auth_permission

auth_user

auth_user_groups

auth_user_groups_permissions

django_admin_log

django_content_type

django_migrations

django_session

games_game

sqlite_sequence

Thegames_gametablepersistsinthedatabasetheGameclasswerecentlycreated,specifically,theGamemodel.Django'sintegratedORMgeneratedthegames_gametablebasedonourGamemodel.Thegames_gametablehasthefollowingrows(alsoknownasfields)withtheirSQLitetypesandallofthemarenotnullable:

id:Theintegerprimarykey,anautoincrementrowcreated:datetimename:varchar(200)release_date:datetimegame_category:varchar(200)played:bool

ThefollowinglinesshowtheSQLcreationscriptthatDjangogeneratedwhenweexecutedthemigrations:

CREATETABLE"games_game"(

"id"integerNOTNULLPRIMARYKEYAUTOINCREMENT,

"created"datetimeNOTNULL,

"name"varchar(200)NOTNULL,

"release_date"datetimeNOTNULL,

"game_category"varchar(200)NOTNULL,

"played"boolNOTNULL

)

DjangogeneratedadditionaltablesthatitrequirestosupporttheWebframeworkandtheauthenticationfeaturesthatwewilluselater.

Page 48: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ManagingserializationanddeserializationOurRESTfulWebAPIhastobeabletoserializeanddeserializethegameinstancesintoJSONrepresentations.WithDjangoRESTFramework,wejustneedtocreateaserializerclassforthegameinstancestomanageserializationtoJSONanddeserializationfromJSON.

DjangoRESTFrameworkusesatwo-phaseprocessforserialization.TheserializersaremediatorsbetweenthemodelinstancesandPythonprimitives.ParserandrenderershandleasmediatorsbetweenPythonprimitivesandHTTPrequestsandresponses.WewillconfigureourmediatorbetweentheGamemodelinstancesandPythonprimitivesbycreatingasubclassoftherest_framework.serializers.Serializerclasstodeclarethefieldsandthenecessarymethodstomanageserializationanddeserialization.WewillrepeatsomeoftheinformationaboutthefieldsthatwehaveincludedintheGamemodelsothatweunderstandallthethingsthatwecanconfigureinasubclassoftheSerializerclass.However,wewillworkwithshortcutsthatwillreduceboilerplatecodelaterinthenextexamples.WewillwritelesscodeinthenextexamplesbyusingtheModelSerializerclass.

Now,gotothegamesapi/gamesfolderfolderandcreateanewPythoncodefilenamedserializers.py.ThefollowinglinesshowthecodethatdeclaresthenewGameSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder.

fromrest_frameworkimportserializers

fromgames.modelsimportGame

classGameSerializer(serializers.Serializer):

pk=serializers.IntegerField(read_only=True)

name=serializers.CharField(max_length=200)

release_date=serializers.DateTimeField()

game_category=serializers.CharField(max_length=200)

played=serializers.BooleanField(required=False)

defcreate(self,validated_data):

returnGame.objects.create(**validated_data)

defupdate(self,instance,validated_data):

instance.name=validated_data.get('name',instance.name)

instance.release_date=validated_data.get('release_date',

instance.release_date)

instance.game_category=validated_data.get('game_category',

instance.game_category)

instance.played=validated_data.get('played',instance.played)

instance.save()

returninstance

TheGameSerializerclassdeclarestheattributesthatrepresentthefieldsthatwewanttobeserialized.NoticethattheyhaveomittedthecreatedattributethatwaspresentintheGamemodel.Whenthereisacalltotheinheritedsavemethodforthisclass,theoverriddencreateandupdatemethodsdefinehowtocreateormodifyaninstance.Infact,thesemethodsmust

Page 49: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

beimplementedinourclassbecausetheyjustraiseaNotImplementedErrorexceptionintheirbasedeclaration.

Thecreatemethodreceivesthevalidateddatainthevalidated_dataargument.ThecodecreatesandreturnsanewGameinstancebasedonthereceivedvalidateddata.

TheupdatemethodreceivesanexistingGameinstancethatisbeingupdatedandthenewvalidateddataintheinstanceandvalidated_dataarguments.Thecodeupdatesthevaluesfortheattributesoftheinstancewiththeupdatedattributevaluesretrievedfromthevalidateddata,callsthesavemethodfortheupdatedGameinstanceandreturnstheupdatedandsavedinstance.

WecanlaunchourdefaultPythoninteractiveshellandmakealltheDjangoprojectmodulesavailablebeforeitstarts.Thisway,wecancheckthattheserializerworksasexpected.Inaddition,itwillhelpusunderstandinghowserializationworksinDjango.Runthefollowingcommandtolaunchtheinteractiveshell.MakesureyouarewithinthegamesapifolderintheTerminalorcommandprompt:

pythonmanage.pyshell

Youwillnoticethatalinethatsays(InteractiveConsole)isdisplayedaftertheusuallinesthatintroduceyourdefaultPythoninteractiveshell.EnterthefollowingcodeinthePythoninteractiveshelltoimportallthethingswewillneedtotesttheGamemodelanditsserializer.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

fromdatetimeimportdatetime

fromdjango.utilsimporttimezone

fromdjango.utils.siximportBytesIO

fromrest_framework.renderersimportJSONRenderer

fromrest_framework.parsersimportJSONParser

fromgames.modelsimportGame

fromgames.serializersimportGameSerializer

EnterthefollowingcodetocreatetwoinstancesoftheGamemodelandsavethem.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

gamedatetime=timezone.make_aware(datetime.now(),

timezone.get_current_timezone())

game1=Game(name='SmurfsJungle',release_date=gamedatetime,game_category='2D

mobilearcade',played=False)

game1.save()

game2=Game(name='AngryBirdsRPG',release_date=gamedatetime,game_category='3D

RPG',played=False)

game2.save()

Afterweexecutetheprecedingcode,wecanchecktheSQLitedatabasewiththepreviouslyintroducecommand-lineorGUItooltocheckthecontentsofthegames_gametable.Wewill

Page 50: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

noticethetablehastworowsandthecolumnshavethevalueswehaveprovidedtothedifferentattributesoftheGameinstances.

EnterthefollowingcommandsintheinteractiveshelltocheckthevaluesfortheprimarykeysoridentifiersforthesavedGameinstancesandthevalueofthecreatedattributeincludesthedateandtimeinwhichwesavedtheinstancetothedatabase.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

print(game1.pk)

print(game1.name)

print(game1.created)

print(game2.pk)

print(game2.name)

print(game2.created)

Now,let'swritethefollowingcodetoserializethefirstgameinstance(game1).Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

game_serializer1=GameSerializer(game1)

print(game_serializer1.data)

Thefollowinglineshowsthegenerateddictionary,specifically,arest_framework.utils.serializer_helpers.ReturnDictinstance:

{'release_date':'2016-05-18T03:02:00.776594Z','game_category':'2Dmobile

arcade','played':False,'pk':2,'name':'SmurfsJungle'}

Now,let'sserializethesecondgameinstance(game2).Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

game_serializer2=GameSerializer(game2)

print(game_serializer2.data)

Thefollowinglineshowsthegenerateddictionary:

{'release_date':'2016-05-18T03:02:00.776594Z','game_category':'3DRPG',

'played':False,'pk':3,'name':'AngryBirdsRPG'}

WecaneasilyrenderthedictionariesholdinthedataattributeintoJSONwiththehelpoftherest_framework.renderers.JSONRendererclass.ThefollowinglinescreateaninstanceofthisclassandthencallstherendermethodtorenderthedictionariesholdinthedataattributeintoJSON.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

renderer=JSONRenderer()

rendered_game1=renderer.render(game_serializer1.data)

rendered_game2=renderer.render(game_serializer2.data)

print(rendered_game1)

print(rendered_game2)

Page 51: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thefollowinglinesshowtheoutputgeneratedfromthetwocallstotherendermethod:

b'{"pk":2,"name":"SmurfsJungle","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"2Dmobilearcade","played":false}'

b'{"pk":3,"name":"AngryBirdsRPG","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"3DRPG","played":false}'

Now,wewillworkintheoppositedirection:fromserializeddatatothepopulationofaGameinstance.ThefollowinglinesgenerateanewGameinstancefromaJSONstring(serializeddata),thatis,theywilldeserialize.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile:

json_string_for_new_game='{"name":"TombRaiderExtreme

Edition","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D

RPG","played":false}'

json_bytes_for_new_game=bytes(json_string_for_new_game,encoding="UTF-8")

stream_for_new_game=BytesIO(json_bytes_for_new_game)

parser=JSONParser()

parsed_new_game=parser.parse(stream_for_new_game)

print(parsed_new_game)

ThefirstlinecreatesanewstringwiththeJSONthatdefinesanewgame(json_string_for_new_game).Then,thecodeconvertsthestringtobytesandsavestheresultsoftheconversioninthejson_bytes_for_new_gamevariable.Thedjango.utils.six.BytesIOclassprovidesabufferedI/Oimplementationusinganin-memorybytesbuffer.ThecodeusesthisclasstocreateastreamfromthepreviouslygeneratedJSONbyteswiththeserializeddata,json_bytes_for_new_game,andsavesthegeneratedinstanceinthestream_for_new_gamevariable.

WecaneasilydeserializeandparseastreamintothePythonmodelswiththehelpoftherest_framework.parsers.JSONParserclass.Thenextlinecreatesaninstanceofthisclassandthencallstheparsemethodwithstream_for_new_gameasanargument,parsesthestreamintoPythonnativedatatypesandsavestheresultsintheparsed_new_gamevariable.

Afterexecutingtheprecedinglines,parsed_new_gameholdsaPythondictionary,parsedfromthestream.Thefollowinglinesshowtheoutputgeneratedafterexecutingtheprecedingcodesnippet:

{'release_date':'2016-05-18T03:02:00.776594Z','played':False,

'game_category':'3DRPG','name':'TombRaiderExtremeEdition'}

ThefollowinglinesusetheGameSerializerclasstogenerateafullypopulatedGameinstancenamednew_gamefromthePythondictionary,parsedfromthestream.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder,intheserializers_test_01.pyfile.

new_game_serializer=GameSerializer(data=parsed_new_game)

ifnew_game_serializer.is_valid():

new_game=new_game_serializer.save()

print(new_game.name)

Page 52: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

First,thecodecreatesaninstanceoftheGameSerializerclasswiththePythondictionarythatwepreviouslyparsedfromthestream(parsed_new_game)passedasthedatakeywordargument.Then,thecodecallstheis_validmethodtodeterminewhetherthedataisvalid.Noticethatwemustalwayscallis_validbeforeweattempttoaccesstheserializeddatarepresentationwhenwepassadatakeywordargumentinthecreationofaserializer.

Ifthemethodreturnstrue,wecanaccesstheserializedrepresentationinthedataattribute,andtherefore,thecodecallsthesavemethodthatinsertsthecorrespondingrowinthedatabaseandreturnsafullypopulatedGameinstance,savedinthenew_gamelocalvariable.Then,thecodeprintsoneoftheattributesfromthefullypopulatedGameinstance.Afterexecutingtheprecedingcode,wefullypopulatedtwoGameinstances:new_game1_instanceandnew_game2_instance.

Tip

Aswecanlearnfromtheprecedingcode,DjangoRESTFrameworkmakesiteasytoserializefromobjectstoJSONanddeserializefromJSONtoobjects,whicharecorerequirementsforourRESTfulWebAPIthathastoperformCRUDoperations.

EnterthefollowingcommandtoleavetheshellwiththeDjangoprojectmodulesthatwestartedtotestserializationanddeserialization:

quit()

Page 53: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WritingAPIviewsNow,wewillcreateDjangoviewsthatwillusethepreviouslycreatedGameSerializerclasstoreturnJSONrepresentationsforeachHTTPrequestthatourAPIwillhandle.Openthegames/views.pyfile.Thefollowinglinesshowtheinitialcodeforthisfile,withjustoneimportstatementandacommentthatindicatesweshouldcreatetheviews.

fromdjango.shortcutsimportrender

#Createyourviewshere.

ThefollowinglinesshowthenewcodethatcreatesaJSONResponseclassanddeclarestwofunctions:game_listandgame_detail,inthegames/views.pyfile.WearecreatingourfirstversionoftheAPI,andweusefunctionstokeepthecodeassimpleaspossible.Wewillworkwithclassesandmorecomplexcodeinthenextexamples.Thehighlightedlinesshowtheexpressionsthatevaluatethevalueoftherequest.methodattributetodeterminetheactionstobeperformedbasedontheHTTPverb.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

fromdjango.httpimportHttpResponse

fromdjango.views.decorators.csrfimportcsrf_exempt

fromrest_framework.renderersimportJSONRenderer

fromrest_framework.parsersimportJSONParser

fromrest_frameworkimportstatus

fromgames.modelsimportGame

fromgames.serializersimportGameSerializer

classJSONResponse(HttpResponse):

def__init__(self,data,**kwargs):

content=JSONRenderer().render(data)

kwargs['content_type']='application/json'

super(JSONResponse,self).__init__(content,**kwargs)

@csrf_exempt

defgame_list(request):

ifrequest.method=='GET':

games=Game.objects.all()

games_serializer=GameSerializer(games,many=True)

returnJSONResponse(games_serializer.data)

elifrequest.method=='POST':

game_data=JSONParser().parse(request)

game_serializer=GameSerializer(data=game_data)

ifgame_serializer.is_valid():

game_serializer.save()

returnJSONResponse(game_serializer.data,

status=status.HTTP_201_CREATED)

returnJSONResponse(game_serializer.errors,

status=status.HTTP_400_BAD_REQUEST)

Page 54: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

@csrf_exempt

defgame_detail(request,pk):

try:

game=Game.objects.get(pk=pk)

exceptGame.DoesNotExist:

returnHttpResponse(status=status.HTTP_404_NOT_FOUND)

ifrequest.method=='GET':

game_serializer=GameSerializer(game)

returnJSONResponse(game_serializer.data)

elifrequest.method=='PUT':

game_data=JSONParser().parse(request)

game_serializer=GameSerializer(game,data=game_data)

ifgame_serializer.is_valid():

game_serializer.save()

returnJSONResponse(game_serializer.data)

returnJSONResponse(game_serializer.errors,

status=status.HTTP_400_BAD_REQUEST)

elifrequest.method=='DELETE':

game.delete()

returnHttpResponse(status=status.HTTP_204_NO_CONTENT)

TheJSONResponseclassisasubclassofthedjango.http.HttpResponseclass.ThesuperclassrepresentsanHTTPresponsewithastringascontent.TheJSONResponseclassrendersitscontentintoJSON.Theclassdefinesjustdeclarethe__init__methodthatcreatedarest_framework.renderers.JSONRendererinstanceandcallsitsrendermethodtorenderthereceiveddataintoJSONsavethereturnedbytestringinthecontentlocalvariable.Then,thecodeaddsthe'content_type'keytotheresponseheaderwith'application/json'asitsvalue.Finally,thecodecallstheinitializerforthebaseclasswiththeJSONbytestringandthekey-valuepairaddedtotheheader.Thisway,theclassrepresentsaJSONresponsethatweuseinthetwofunctionstoeasilyreturnaJSONresponse.

Thecodeusesthe@csrf_exemptdecoratorinthetwofunctionstoensurethattheviewsetsaCross-SiteRequestForgery(CSRF)cookie.Wedothistomakeitsimpletotestthisexamplethatdoesn'trepresentaproduction-readyWebService.WewilladdsecurityfeaturestoourRESTfulAPIlater.

WhentheDjangoserverreceivesanHTTPrequest,DjangocreatesanHttpRequestinstance,specificallyadjango.http.HttpRequestobject.Thisinstancecontainsmetadataabouttherequest,includingtheHTTPverb.ThemethodattributeprovidesastringrepresentingtheHTTPverbormethodusedintherequest.

WhenDjangoloadstheappropriateviewthatwillprocesstherequests,itpassestheHttpRequestinstanceasthefirstargumenttotheviewfunction.TheviewfunctionhastoreturnanHttpResponseinstance,specificallyadjango.http.HttpResponseinstance.

Thegame_listfunctionlistsallthegamesorcreatesanewgame.Thefunctionreceivesan

Page 55: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HttpRequestinstanceintherequestargument.ThefunctioniscapableofprocessingtwoHTTPverbs:GETandPOST.Thecodechecksthevalueoftherequest.methodattributetodeterminethecodetobeexecutedbasedontheHTTPverb.IftheHTTPverbisGET,theexpressionrequest.method=='GET'willevaluatetoTrueandthecodehastolistallthegames.ThecodewillretrievealltheGameobjectsfromthedatabase,usetheGameSerializertoserializeallofthem,andreturnaJSONResponseinstancebuiltwiththedatageneratedbytheGameSerializer.ThecodecreatestheGameSerializerinstancewiththemany=Trueargumenttospecifythatmultipleinstanceshavetobeserializedandnotjustone.Underthehoods,DjangousesaListSerializerwhenthemanyargumentvalueissettoTrue.

IftheHTTPverbisPOST,thecodehastocreateanewgamebasedontheJSONdatathatisincludedintheHTTPrequest.First,thecodeusesaJSONParserinstanceandcallsitsparsemethodwithrequestasanargumenttoparsethegamedataprovidedasJSONdataintherequestandsavestheresultsinthegame_datalocalvariable.Then,thecodecreatesaGameSerializerinstancewiththepreviouslyretrieveddataandcallstheis_validmethodtodeterminewhethertheGameinstanceisvalidornot.Iftheinstanceisvalid,thecodecallsthesavemethodtopersisttheinstanceinthedatabaseandreturnsaJSONResponsewiththesaveddatainitsbodyandastatusequaltostatus.HTTP_201_CREATED,thatis,201Created.

Tip

Wheneverwehavetoreturnaspecificstatusdifferentfromthedefault200OKstatus,itisagoodpracticetousethemodulevariablesdefinedintherest_framework.statusmoduleandtoavoidusinghardcodednumericvalues.

Thegame_detailfunctionretrieves,updatesordeletesanexistinggame.ThefunctionreceivesanHttpRequestinstanceintherequestargumentandtheprimarykeyoridentifierforthegametoberetrieved,updatedordeletedinthepkargument.ThefunctioniscapableofprocessingthreeHTTPverbs:GET,PUTandDELETE.Thecodechecksthevalueoftherequest.methodattributetodeterminethecodetobeexecutedbasedontheHTTPverb.NomatterwhichistheHTTPverb,thefunctioncallstheGame.objects.getmethodwiththereceivedpkasthepkargumenttoretrieveaGameinstancefromthedatabasebasedonthespecifiedprimarykeyoridentifier,andsavesitinthegamelocalvariable.Incaseagamewiththespecifiedprimarykeyoridentifierdoesn'texistinthedatabase,thecodereturnsanHttpResponsewithitsstatusequaltostatus.HTTP_404_NOT_FOUND,thatis,404NotFound.

IftheHTTPverbisGET,thecodecreatesaGameSerializerinstancewithgameasanargumentandreturnsthedatafortheserializedgameinaJSONResponsethatwillincludethedefault200OKstatus.ThecodereturnstheretrievedgameserializedasJSON.

IftheHTTPverbisPUT,thecodehastocreateanewgamebasedontheJSONdatathatisincludedintheHTTPrequestanduseittoreplaceanexistinggame.First,thecodeusesaJSONParserinstanceandcallsitsparsemethodwithrequestasanargumenttoparsethegamedataprovidedasJSONdataintherequestandsavestheresultsinthegame_datalocalvariable.Then,thecodecreatesaGameSerializerinstancewiththeGameinstancepreviouslyretrieved

Page 56: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

fromthedatabase(game)andtheretrieveddatathatwillreplacetheexistingdata(game_data).Then,thecodecallstheis_validmethodtodeterminewhethertheGameinstanceisvalidornot.Iftheinstanceisvalid,thecodecallsthesavemethodtopersisttheinstancewiththereplacedvaluesinthedatabaseandreturnsaJSONResponsewiththesaveddatainitsbodyandthedefault200OKstatus.Iftheparseddatadoesn'tgenerateavalidGameinstance,thecodereturnsaJSONResponsewithastatusequaltostatus.HTTP_400_BAD_REQUEST,thatis,400BadRequest.

IftheHTTPverbisDELETE,thecodecallsthedeletemethodfortheGameinstancepreviouslyretrievedfromthedatabase(game).Thecalltothedeletemethoderasestheunderlyingrowinthegames_gametable,andtherefore,thegamewon'tbeavailableanymore.Then,thecodereturnsaJSONResponsewithastatusequaltostatus.HTTP_204_NO_CONTENTthatis,204NoContent.

Now,wehavetocreateanewPythonfilenamedurls.pyinthegamesfolder,specifically,thegames/urls.pyfile.ThefollowinglinesshowthecodeforthisfilethatdefinestheURLpatternsthatspecifiestheregularexpressionsthathavetobematchedintherequesttorunaspecificfunctiondefinesintheviews.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

fromdjango.conf.urlsimporturl

fromgamesimportviews

urlpatterns=[

url(r'^games/$',views.game_list),

url(r'^games/(?P<pk>[0-9]+)/$',views.game_detail),

]

TheurlpatternslistmakesitpossibletorouteURLstoviews.Thecodecallsthedjango.conf.urls.urlfunctionwiththeregularexpressionthathastobematchedandtheviewfunctiondefinedintheviewsmoduleasargumentstocreateaRegexURLPatterninstanceforeachentryintheurlpatternslist.

Wehavetoreplacethecodeintheurls.pyfileinthegamesapifolder,specifically,thegamesapi/urls.pyfile.ThefiledefinestherootURLconfigurations,andtherefore,wemustincludetheURLpatternsdeclaredinthepreviouslycodedgames/urls.pyfile.Thefollowinglinesshowthenewcodeforthegamesapi/urls.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_01_01folder:

fromdjango.conf.urlsimporturl,include

urlpatterns=[

url(r'^',include('games.urls')),

]

Now,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequeststoourunsecureWebAPI(wewilldefinitelyaddsecuritylater).Executethefollowingcommand:

Page 57: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

pythonmanage.pyrunserver

Thefollowinglinesshowtheoutputafterweexecutetheprecedingcommand.Thedevelopmentserverislisteningatport8000.

Performingsystemchecks...

Systemcheckidentifiednoissues(0silenced).

May20,2016-04:22:38

Djangoversion1.9.6,usingsettings'gamesapi.settings'

Startingdevelopmentserverathttp://127.0.0.1:8000/

QuittheserverwithCONTROL-C.

Withtheprecedingcommand,wewillstartDjangodevelopmentserverandwewillonlybeabletoaccessitinourdevelopmentcomputer.TheprecedingcommandstartsthedevelopmentserverinthedefaultIPaddress,thatis,127.0.0.1(localhost).ItisnotpossibletoaccessthisIPaddressfromothercomputersordevicesconnectedonourLAN.Thus,ifwewanttomakeHTTPrequeststoourAPIfromothercomputersordevicesconnectedtoourLAN,weshouldusethedevelopmentcomputerIPaddress,0.0.0.0(forIPv4configurations),or::(forIPv6configurations)asthedesiredIPaddressforourdevelopmentserver.

Ifwespecify0.0.0.0asthedesiredIPaddressforIPv4configurations,thedevelopmentserverwilllistenoneveryinterfaceonport8000.Whenwespecify::forIPv6configurations,itwillhavethesameeffect.Inaddition,itisnecessarytoopenthedefaultport8000inourfirewalls(softwareand/orhardware)andconfigureport-forwardingtothecomputerthatisrunningthedevelopmentserver.ThefollowingcommandlaunchesDjango'sdevelopmentserverinanIPv4configurationandallowsrequeststobemadefromothercomputersanddevicesconnectedtoourLAN:

pythonmanage.pyrunserver0.0.0.0:8000

Tip

IfyoudecidetocomposeandsendHTTPrequestsfromothercomputersordevicesconnectedtotheLAN,rememberthatyouhavetousethedevelopmentcomputer'sassignedIPaddressinsteadoflocalhost.Forexample,ifthecomputer'sassignedIPv4IPaddressis192.168.1.106,insteadoflocalhost:8000,youshoulduse192.168.1.106:8000.Ofcourse,youcanalsousethehostnameinsteadoftheIPaddress.ThepreviouslyexplainedconfigurationsareveryimportantbecausemobiledevicesmightbetheconsumersofourRESTfulAPIsandwewillalwayswanttotesttheappsthatmakeuseofourAPIsinourdevelopmentenvironments.

Page 58: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MakingHTTPrequeststotheAPITheDjangodevelopmentserverisrunningonlocalhost(127.0.0.1),listeningonport8000,andwaitingforourHTTPrequests.Now,wewillcomposeandsendHTTPrequestslocallyinourdevelopmentcomputerorfromothercomputerordevicesconnectedtoourLAN.WewillusethefollowingdifferentkindoftoolstocomposeandsendHTTPrequeststhroughoutourbook.

Command-linetoolsGUItoolsPythoncodeJavaScriptcode

Tip

NoticethatyoucanuseanyotherapplicationthatallowsyoutocomposeandsendHTTPrequests.Therearemanyappsthatrunontabletsandsmartphonesthatallowyoutoaccomplishthistask.However,wewillfocusourattentiononthemostusefultoolswhenbuildingRESTfulWebAPIs.

Page 59: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Workingwithcommand-linetools-curlandhttpieWewillstartwithcommand-linetools.Oneofthekeyadvantagesofcommand-linetoolsisthatwecaneasilyrunagaintheHTTPrequestsafterwebuiltthemforthefirsttime,andwedon'tneedtousethemouseortapthescreentorunrequests.Wecanalsoeasilybuildascriptwithbatchrequestsandrunthem.Ashappenswithanycommand-linetool,itcantakemoretimetoperformthefirstrequestscomparedwithGUItools,butitbecomeseasieronceweperformedmanyrequestsandwecaneasilyreusethecommandswehavewritteninthepasttocomposenewrequests.

Curl,alsoknownascURL,isaverypopularopensourcecommand-linetoolandlibrarythatallowustoeasilytransferdata.Wecanusethecurlcommand-linetooltoeasilycomposeandsendHTTPrequestsandchecktheirresponses.

Tip

IfyouareworkingoneithermacOSorLinux,youcanopenaTerminalandstartusingcurlfromthecommandline.IfyouareworkingonanyWindowsversion,youcaneasilyinstallcurlfromtheCygwinpackageinstallationoption,andexecuteitfromtheCygwinterminal.Youcanreadmoreaboutthecurlutilityathttp://curl.haxx.se.YoucanreadmoreabouttheCygwinterminalanditsinstallationprocedureathttp://cygwin.com/install.html.

OpenaCygwinterminalinWindowsoraterminalinmacOSorLinux,andrunthefollowingcommand.Itisveryimportantthatyouentertheendingslash(/)because/gameswon'tmatchanyofthepatternsspecifiedinurlpatternsinthegames/urls.pyfile.WeareusingthedefaultconfigurationforDjangothatdoesn'tredirectURLsthatdon'tmatchanyofthepatternstothesameURLswithaslashappended.Thus,wemustenter/games/,includingtheendingslash(/):

curl-XGET:8000/games/

TheprecedingcommandwillcomposeandsendthefollowingHTTPrequest-GEThttp://localhost:8000/games/.TherequestisthesimplestcaseinourRESTfulAPIbecauseitwillmatchandruntheviews.game_listfunction,thatis,thegame_listfunctiondeclaredwithinthegames/views.pyfile.ThefunctionjustreceivesrequestasaparameterbecausetheURLpatterndoesn'tincludeanyparameters.AstheHTTPverbfortherequestisGET,therequest.methodpropertyisequalto'GET',andtherefore,thefunctionwillexecutethecodethatretrievesalltheGameobjectsandgeneratesaJSONresponsewithalloftheseGameobjectsserialized.

ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthreeGameobjectsintheJSONresponse:

[{"pk":3,"name":"AngryBirdsRPG","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"3DRPG","played":false},

{"pk":2,"name":"SmurfsJungle","release_date":"2016-05-

Page 60: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

18T03:02:00.776594Z","game_category":"2Dmobilearcade","played":false},

{"pk":11,"name":"TombRaiderExtremeEdition","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"3DRPG","played":false}]

Aswemightnoticefromthepreviousresponse,thecurlutilitydisplaystheJSONresponseinasingleline,andtherefore,itisabitdifficulttoreadit.Inthiscase,weknowthattheContent-Typeoftheresponseisapplication/json.However,incasewewanttohavemoredetailsabouttheresponse,wecanusethe-ioptiontorequestcurltoprinttheHTTPresponseheaders.Wecancombinethe-iand-Xoptionsbyusing-iX.

GobacktotheCygwinterminalinWindowsortheTerminalinmacOSorLinux,andrunthefollowingcommand:

curl-iXGET:8000/games/

ThefollowinglinesshowanexampleresponsefortheHTTPrequest.ThefirstlinesshowtheHTTPresponseheaders,includingthestatus(200OK)andtheContent-type(application/json).AftertheHTTPresponseheaders,wecanseethedetailsforthethreeGameobjectsintheJSONresponse:

HTTP/1.0200OK

Date:Tue,24May201618:04:40GMT

Server:WSGIServer/0.2CPython/3.5.1

Content-Type:application/json

X-Frame-Options:SAMEORIGIN

[{"pk":3,"name":"AngryBirdsRPG","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"3DRPG","played":false},

{"pk":2,"name":"SmurfsJungle","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"2Dmobilearcade","played":false},

{"pk":11,"name":"TombRaiderExtremeEdition","release_date":"2016-05-

18T03:02:00.776594Z","game_category":"3DRPG","played":false}]

Afterwerunthetworequests,wewillseethefollowinglinesinthewindowthatisrunningtheDjangodevelopmentserver.TheoutputindicatesthattheserverreceivedtwoHTTPrequestswiththeGETverband/games/astheURI.TheserverprocessedbothHTTPrequests,returnedstatuscode200andtheresponselengthwasequalto379characters.Theresponselengthcanbedifferentbecausethevaluefortheprimarykeyassignedtoeachgamewillhaveanincidenceintheresponselength.ThefirstnumberafterHTTP/1.1."indicatesthereturnedstatuscode(200)andthesecondnumbertheresponselength(379).

[25/May/201604:35:09]"GET/games/HTTP/1.1"200379

[25/May/201604:35:10]"GET/games/HTTP/1.1"200379

Thefollowingimageshowstwoterminalwindowsside-by-sideonmacOS.TheTerminalwindowattheleft-handsideisrunningtheDjangodevelopmentserveranddisplaysthereceivedandprocessedHTTPrequests.TheTerminalwindowattheright-handsideisrunningcurlcommandstogeneratetheHTTPrequests.

Itisagoodideatouseasimilarconfigurationtochecktheoutputwhilewecomposeandsend

Page 61: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

theHTTPrequests.NoticethattheJSONoutputsareabitdifficulttoreadbecausetheydon'tusesyntaxhighlighting:

Now,wewillinstallHTTPie,acommand-lineHTTPclientwritteninPythonthatmakesiteasytosendHTTPrequestsandusesasyntaxthatiseasierthancurl(alsoknownascURL).OneofthegreatadvantagesofHTTPieisthatitdisplayscolorizedoutputandusesmultiplelinestodisplaytheresponsedetails.Thus,HTTPiemakesiteasiertounderstandtheresponsesthanthecurlutility.WejustneedtoactivatethevirtualenvironmentandthenrunthefollowingcommandintheterminalorcommandprompttoinstalltheHTTPiepackage:

pipinstall--upgradehttpie

Thelastlinesfortheoutputwillindicatethatthedjangopackagehasbeensuccessfullyinstalled.

Collectinghttpie

Downloadinghttpie-0.9.3-py2.py3-none-any.whl(66kB)

Collectingrequests>=2.3.0(fromhttpie)

Usingcachedrequests-2.10.0-py2.py3-none-any.whl

CollectingPygments>=1.5(fromhttpie)

UsingcachedPygments-2.1.3-py2.py3-none-any.whl

Installingcollectedpackages:requests,Pygments,httpie

SuccessfullyinstalledPygments-2.1.3httpie-0.9.3requests-2.10.0

Tip

Incaseyoudon'trememberhowtoactivatethevirtualenvironmentthatwecreatedforthisexample,readthefollowingsectioninthischapter-SettingupthevirtualenvironmentwithDjangoRESTframework.

Now,wecanuseanhttpcommandtoeasilycomposeandsendHTTPrequeststo

Page 62: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

localhost:8000andtesttheRESTfulAPIbuiltwithDjangoRESTframework.HTTPiesupportscurl-likeshorthandsforlocalhost,andtherefore,wecanuse:8000asashorthandthatexpandstohttp://localhost:8000.Runthefollowingcommandandremembertoentertheendingslash(/):

http:8000/games/

TheprecedingcommandwillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/games/.Therequestisthesameonewehavepreviouslycomposedwiththecurlcommand.However,inthiscase,theHTTPieutilitywilldisplayacolorizedoutputanditwillusemultiplelinestodisplaytheJSONresponse.TheprecedingcommandisequivalenttothefollowingcommandthatspecifiestheGETmethodafterhttp:

httpGET:8000/games/

ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withtheheadersandthethreeGameobjectsintheJSONresponse.ItisindeedeasiertounderstandtheresponsecomparedwiththeresultsgeneratedwhenwecomposedtheHTTPrequestwithcurl.HTTPieautomaticallyformatstheJSONdatareceivedasaresponseandappliessyntaxhighlighting,specifically,bothcolorsandformatting:

HTTP/1.0200OK

Content-Type:application/json

Date:Thu,26May201621:33:17GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

[

{

"game_category":"3DRPG",

"name":"AngryBirdsRPG",

"pk":3,

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

},

{

"game_category":"2Dmobilearcade",

"name":"SmurfsJungle",

"pk":2,

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

},

{

"game_category":"3DRPG",

"name":"TombRaiderExtremeEdition",

"pk":11,

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

]

Tip

Page 63: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Wecanachievethesameresultsbycombiningtheoutputgeneratedwiththecurlcommandwithotherutilities.However,HTTPieprovidesusexactlywhatweneedtoworkwithRESTfulAPIs.WewilluseHTTPietocomposeandsendHTTPrequest,butwewillalwaysprovidetheequivalentcurlcommand.

ThefollowingimageshowstwoTerminalwindowsside-by-sideonmacOS.Theterminalwindowattheleft-handsideisrunningtheDjangodevelopmentserveranddisplaysthereceivedandprocessedHTTPrequests.TheTerminalwindowattheright-handsideisrunningHTTPiecommandstogeneratetheHTTPrequests.NoticethattheJSONoutputiseasiertoreadcomparedtotheoutputgeneratedbythecurlcommand:

WecanexecuteHTTPiewiththe-boptionincasewedon'twanttoincludetheheaderintheresponse.Forexample,thefollowinglineperformsthesameHTTPrequestbutdoesn'tdisplaytheheaderintheresponseoutput,andtherefore,theoutputwilljustdisplaytheJSONresponse:

http-b:8000/games/

Now,wewillselectoneofthegamesfromtheprecedinglistandwewillcomposeanHTTPrequesttoretrievejustthechosengame.Forexample,inthepreviouslist,thefirstgamehasapkvalueequalto3.Runthefollowingcommandtoretrievethisgame.Usethepkvalueyouhaveretrievedinthepreviouscommandforthefirstgame,asthepknumbermightbedifferent:

http:8000/games/3/

Page 64: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/games/3/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/games/3/.Therequesthasanumberafter/games/,andtherefore,itwillmatch'^games/(?P<pk>[0-9]+)/$'andruntheviews.game_detailfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.ThefunctionreceivesrequestandpkasparametersbecausetheURLpatternpassesthenumberspecifiedafter/games/inthepkparameter.AstheHTTPverbfortherequestisGET,therequest.methodpropertyisequalto'GET',andtherefore,thefunctionwillexecutethecodethatretrievestheGameobjectwhoseprimarykeymatchesthepkvaluereceivedasanargumentand,iffound,generatesaJSONresponsewiththisGameobjectserialized.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withtheGameobjectthatmatchesthepkvalueintheJSONresponse:

HTTP/1.0200OK

Content-Type:application/json

Date:Fri,27May201602:28:30GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

{

"game_category":"3DRPG",

"name":"AngryBirdsRPG",

"pk":3,

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

Now,wewillcomposeandsendanHTTPrequesttoretrieveagamethatdoesn'texist.Forexample,intheprecedinglist,thereisnogamewithapkvalueequalto99999.Runthefollowingcommandtotrytoretrievethisgame.Makesureyouuseapkvaluethatdoesn'texist.Wemustmakesurethattheutilitiesdisplaytheheadersaspartoftheresponsebecausetheresponsewon'thaveabody:

http:8000/games/99999/

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/games/99999/

TheprecedingcommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/games/99999/.Therequestisthesamethanthepreviousonewehaveanalyzed,withadifferentnumberforthepkparameter.Theserverwillruntheviews.game_detailfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.ThefunctionwillexecutethecodethatretrievestheGameobjectwhoseprimarykeymatchesthepkvaluereceivedasanargumentandaGame.DoesNotExistexceptionwillbethrownandcapturedbecausethereisnogamewiththespecifiedpkvalue.Thus,thecodewillreturnanHTTP404NotFoundstatuscode.ThefollowinglinesshowanexampleheaderresponsefortheHTTPrequest:

Page 65: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTP/1.0404NotFound

Content-Type:text/html;charset=utf-8

Date:Fri,27May201602:20:41GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

WewillcomposeandsendanHTTPrequesttocreateanewgame.

httpPOST:8000/games/name='PvZ3'game_category='2Dmobilearcade'

played=falserelease_date='2016-05-18T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand.Itisveryimportanttousethe-H"Content-Type:application/json"optiontoindicatecurltosendthedataspecifiedafterthe-doptionasapplication/jsoninsteadofthedefaultapplication/x-www-form-urlencoded:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"PvZ3",

"game_category":"2Dmobilearcade","played":"false","release_date":"2016-

05-18T03:02:00.776594Z"}':8000/games/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:POSThttp://localhost:8000/games/withthefollowingJSONkey-valuepairs:

{

"name":"PvZ3",

"game_category":"2Dmobilearcade",

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

Therequestspecifies/games/,andtherefore,itwillmatch'^games/$'andruntheviews.game_listfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.ThefunctionjustreceivesrequestasaparameterbecausetheURLpatterndoesn'tincludeanyparameters.AstheHTTPverbfortherequestisPOST,therequest.methodpropertyisequalto'POST',andtherefore,thefunctionwillexecutethecodethatparsestheJSONdatareceivedintherequest,createsanewGameand,ifthedataisvalid,itsavesthenewGame.IfthenewGamewassuccessfullypersistedinthedatabase,thefunctionreturnsanHTTP201CreatedstatuscodeandtherecentlypersistedGameserializedserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewGameobjectintheJSONresponse:

HTTP/1.0201Created

Content-Type:application/json

Date:Fri,27May201605:12:39GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

{

"game_category":"2Dmobilearcade",

"name":"PvZ3",

"pk":15,

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

Page 66: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,wewillcomposeandsendanHTTPrequesttoupdateanexistinggame,specifically,thepreviouslyaddedgame.Wehavetocheckthevalueassignedtopkinthepreviousresponseandreplace15inthecommandwiththereturnedvalue.Forexample,incasethevalueforpkwas5,youshoulduse:8000/games/5/insteadof:8000/games/15/.

httpPUT:8000/games/15/name='PvZ3'game_category='2Dmobilearcade'

played=truerelease_date='2016-05-20T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand.Ashappenedwiththepreviouscurlexample,itisveryimportanttousethe-H"Content-Type:application/json"optiontoindicatecurltosendthedataspecifiedafterthe-doptionasapplication/jsoninsteadofthedefaultapplication/x-www-form-urlencoded:

curl-iXPUT-H"Content-Type:application/json"-d'{"name":"PvZ3",

"game_category":"2Dmobilearcade","played":"true","release_date":"2016-

05-20T03:02:00.776594Z"}':8000/games/15/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:PUThttp://localhost:8000/games/15/withthefollowingJSONkey-valuepairs:

{

"name":"PvZ3",

"game_category":"2Dmobilearcade",

"played":true,

"release_date":"2016-05-20T03:02:00.776594Z"

}

Therequesthasanumberafter/games/,andtherefore,itwillmatch'^games/(?P<pk>[0-9]+)/$'andruntheviews.game_detailfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.ThefunctionreceivesrequestandpkasparametersbecausetheURLpatternpassesthenumberspecifiedafter/games/inthepkparameter.AstheHTTPverbfortherequestisPUT,therequest.methodpropertyisequalto'PUT',andtherefore,thefunctionwillexecutethecodethatparsestheJSONdatareceivedintherequest,createsaGameinstancefromthisdataandupdatestheexistinggameinthedatabase.Ifthegamewassuccessfullyupdatedinthedatabase,thefunctionreturnsanHTTP200OKstatuscodeandtherecentlyupdatedGameserializedserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withtheupdatedGameobjectintheJSONresponse:

HTTP/1.0200OK

Content-Type:application/json

Date:Sat,28May201600:49:05GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

{

"game_category":"2Dmobilearcade",

"name":"PvZ3",

"pk":15,

"played":true,

"release_date":"2016-05-20T03:02:00.776594Z"

}

Page 67: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

InordertosuccessfullyprocessaPUTHTTPrequestthatupdatesanexistinggame,wemustprovidevaluesforalltherequiredfields.WewillcomposeandsendanHTTPrequesttotryupdateanexistinggame,andwewillfailtodosobecausewewilljustprovideavalueforthename.Ashappenedinthepreviousrequest,wewillusethevalueassignedtopkinthelastgameweadded:

httpPUT:8000/games/15/name='PvZ4'

Thefollowingistheequivalentcurlcommand:

curl-iXPUT-H"Content-Type:application/json"-d'{"name":"PvZ4"}'

:8000/games/15/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:PUThttp://localhost:8000/games/15/withthefollowingJSONkey-valuepair:

{

"name":"PvZ4",

}

Therequestwillexecutethesamecodeweexplainedforthepreviousrequest.Becausewedidn'tprovidealltherequiredvaluesforaGameinstance,thegame_serializer.is_valid()methodwillreturnFalseandthefunctionwillreturnanHTTP400BadRequeststatuscodeandthedetailsgeneratedinthegame_serializer.errorsattributeserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withtherequiredfieldsthatourrequestdidn'tincludevaluesintheJSONresponse:

HTTP/1.0400BadRequest

Content-Type:application/json

Date:Sat,28May201602:53:08GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

{

"game_category":[

"Thisfieldisrequired."

],

"release_date":[

"Thisfieldisrequired."

]

}

Tip

WhenwewantourAPItobeabletoupdateasinglefieldforanexistingresource,inthiscase,anexistinggame,weshouldprovideanimplementationforthePATCHmethod.ThePUTmethodismeanttoreplaceanentireresourceandthePATCHmethodismeanttoapplyadeltatoanexistingresource.WecanwritecodeinthehandlerforthePUTmethodapplyadeltatoanexistingresource,butitisabetterpracticetousethePATCHmethodforthisspecifictask.WewillworkwiththePATCHmethodlater.

Page 68: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,wewillcomposeandsendanHTTPrequesttodeleteanexistinggame,specifically,thelastgameweadded.AshappenedinourlastHTTPrequests,wehavetocheckthevalueassignedtopkinthepreviousresponseandreplace12inthecommandwiththereturnedvalue:

httpDELETE:8000/games/15/

Thefollowingistheequivalentcurlcommand:

curl-iXDELETE:8000/games/15/

TheprecedingcommandswillcomposeandsendthefollowingHTTPrequest:DELETEhttp://localhost:8000/games/15/.Therequesthasanumberafter/games/,andtherefore,itwillmatch'^games/(?P<pk>[0-9]+)/$'andruntheviews.game_detailfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.ThefunctionreceivesrequestandpkasparametersbecausetheURLpatternpassesthenumberspecifiedafter/games/inthepkparameter.AstheHTTPverbfortherequestisDELETE,therequest.methodpropertyisequalto'DELETE',andtherefore,thefunctionwillexecutethecodethatparsestheJSONdatareceivedintherequest,createsaGameinstancefromthisdataanddeletestheexistinggameinthedatabase.Ifthegamewassuccessfullydeletedinthedatabase,thefunctionreturnsanHTTP204NoContentstatuscode.ThefollowinglinesshowanexampleresponsefortheHTTPrequestaftersuccessfullydeletinganexistinggame:

HTTP/1.0204NoContent

Date:Sat,28May201604:08:58GMT

Server:WSGIServer/0.2CPython/3.5.1

Content-Length:0

X-Frame-Options:SAMEORIGIN

Content-Type:text/html;charset=utf-8

Page 69: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithGUItools-PostmanandothersSofar,wehavebeenworkingwithtwoterminal-basedorcommand-linetoolstocomposeandsendHTTPrequeststoourDjangodevelopmentserver-cURLandHTTPie.Now,wewillworkwithGUI(GraphicalUserInterface)tools.

PostmanisaverypopularAPItestingsuiteGUItoolthatallowsustoeasilycomposeandsendHTTPrequests,amongotherfeatures.PostmanisavailableasaChromeAppandasaMacApp.WecanexecuteitinWindows,LinuxandmacOSasaChromeApp,thatis,anapplicationrunningontopofGoogleChrome.IncaseweworkwithmacOS,wecanusetheMacAppinsteadoftheChromeApp.YoucandownloadtheversionsofthePostmanAppfromthefollowingURL-https://www.getpostman.com.

Tip

YoucandownloadandinstallPostmanforfreetocomposeandsendHTTPrequeststoourRESTfulAPIs.YoujustneedtosignuptoPostmanandwewon'tbeusinganyofthepaidfeaturesprovidedbyPostmancloudinourexamples.AlltheinstructionsworkwithPostman4.2.2orgreater.

Now,wewillusetheBuildertabinPostmantoeasilycomposeandsendHTTPrequeststolocalhost:8000andtesttheRESTfulAPIwiththisGUItool.Postmandoesn'tsupportcurl-likeshorthandsforlocalhost,andtherefore,wecannotusethesameshorthandswehavebeenusingwhencomposingrequestswithHTTPie.

SelectGET inthedropdownmenuattheleft-handsideoftheEnterrequestURLtextbox,andenterlocalhost:8000/games/inthistextboxattheright-handsideofthedropdown.Then,clickSendandPostmanwilldisplaytheStatus(200OK),thetimeittookfortherequesttobeprocessedandtheresponsebodywithallthegamesformattedasJSONwithsyntaxhighlighting(Prettyview).

ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPGETrequest:

Page 70: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ClickonHeadersattheright-handsideofBodyandCookiestoreadtheresponseheaders.ThefollowingscreenshotshowsthelayoutfortheresponseheadersthatPostmandisplaysfortheprecedingresponse.NoticethatPostmandisplaystheStatusattheright-handsideoftheresponseanddoesn'tincludeitasthefirstlineoftheHeaders,ashappenedwhenweworkedwithboththecURLandHTTPieutilities:

Now,wewillusetheBuildertabinPostmantocomposeandsendanHTTPrequesttocreateanewgame,specifically,aPOSTrequest.Followthenextsteps:

Page 71: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

1. SelectPOST inthedrop-downmenuattheleft-handsideoftheEnterrequestURLtextbox,andenterlocalhost:8000/games/inthistextboxattheright-handsideofthedropdown.

2. ClickBodyattheright-handsideofAuthorizationandHeaders,withinthepanelthatcomposestherequest.

3. ActivatetherawradiobuttonandselectJSON(application/json)inthedropdownattheright-handsideofthebinaryradiobutton.PostmanwillautomaticallyaddaContent-typeasapplication/jsonheader,andtherefore,youwillnoticetheHeaderstabwillberenamedtoHeaders(1),indicatingusthatthereisonekey-valuepairspecifiedfortherequestheaders.

4. Enterthefollowinglinesinthetextboxbelowtheradiobuttons,withintheBodytab:

{

"name":"BatmanvsSuperman",

"game_category":"3DRPG",

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

ThefollowingscreenshotshowstherequestbodyinPostman:

WefollowedthenecessarystepstocreateanHTTPPOSTrequestwithaJSONbodythatspecifiesthenecessarykey-valuepairstocreateanewgame.ClickonSendandPostmanwilldisplaytheStatus(201Created),thetimeittookfortherequesttobeprocessedandtheresponsebodywiththerecentlyaddedgameformattedasJSONwithsyntaxhighlighting(Prettyview).ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPPOSTrequest.

Tip

Page 72: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

IfwewanttocomposeandsendanHTTPPUTrequestwithPostman,itisnecessarytofollowthepreviouslyexplainedstepstoprovideJSONdatawithintherequestbody.

OneofthenicefeaturesincludedinPostmanisthatwecaneasilyreviewandagainruntheHTTPrequestswehavemadebybrowsingthesavedHistoryshownattheleft-handsideofthePostmanwindow.TheHistorypanedisplaysalistwiththeHTTPverbfollowedbytheURLforeachHTTPrequestwehavecomposedandsent.WejustneedtoclickonthedesiredHTTPrequestandclickSendtorunitagain.ThefollowingscreenshotshowsthemanyHTTPrequestsintheHistorypaneandthefirstoneselectedtosenditagain.

JetBrainsPyCharmisaverypopularmultiplatformPythonIDE(shortforIntegratedDevelopmentEnvironment)availableonmacOS,LinuxandWindows.ItspaidProfessionalversionincludesaRESTClientthatallowsustotestRESTfulWebservices.IncaseweworkwiththisversionoftheIDE,wecancomposeandsendHTTPrequestswithoutleavingtheIDE.Youdon'tneedaJetBrainsPyCharmProfessionalversionlicensetoruntheexamplesincludedinthisbook.However,astheIDEisverypopular,wewilllearnthenecessarystepstocomposeandsendanHTTPrequestforourAPIusingtheRESTClientincludedinthisIDE.

Now,wewillusetheRESTClientincludedinPyCharmprofessionaltocomposeandsendanHTTPrequesttocreateanewgame,specifically,aPOSTrequest.Followthenextsteps:

1. SelectTools|TestRESTfulWebServiceinthemainmenutodisplaytheRESTClientpanel.

2. SelectPOST intheHTTPmethoddropdownmenuintheRESTClientpane.3. Enterlocalhost:8000intheHost/porttextbox,attheright-handsideofthedropdown.4. Enter/games/inthePathtextbox,attheright-handsideoftheHost/porttextbox.5. MakesurethattheRequesttabisactivatedandclickontheadd(+)buttonatthebottom

oftheHeaderslist.TheIDEwilldisplayatextboxforthenameandadropdownforthevalue.EnterContent-TypeinName,enterapplication/jsoninValueandpressEnter.

6. ActivatetheText:radiobuttoninRequestBodyandclickthe...button,ontheright-handsideoftheTexttextbox,tospecifythetexttosend.EnterthefollowinglinesintextboxincludedintheSpecifythetexttosenddialogboxandthenclickonOK.

{

"name":"TeenageMutantNinjaTurtles",

"game_category":"3DRPG",

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

ThefollowingscreenshotshowstherequestbuiltinPyCharmProfessionalRESTClient:

Page 73: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WefollowedthenecessarystepstocreateanHTTPPOSTrequestwithaJSONbodythatspecifiesthenecessarykey-valuepairstocreateanewgame.Clickonthesubmitrequestbutton,thatis,thefirstbuttonwiththeplayiconattheupper-leftcorneroftheRESTClientpane.TheRESTclientwillcomposeandsendtheHTTPPOSTrequest,willactivatetheResponsetab,anddisplaytheresponsecode201(Created),thetimeittookfortherequesttobeprocessed,andthecontentlengthatthebottomofthepane.

Bydefault,theRESTclientwillautomaticallyapplyJSONsyntaxhighlightingtotheresponse.However,sometimes,theJSONcontentisdisplayedwithoutlinebreaksanditisnecessarytoclickonthereformatresponsebutton,thatis,thefirstbuttonintheResponsetab.TheRESTclientdisplaystheresponseheadersinanothertab,andtherefore,itjustdisplaystheresponsebodyintheResponsetab.ThefollowingscreenshotshowstheJSONresponsebodyintheRESTclientfortheHTTPPOSTrequest:

Tip

Page 74: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

IfwewanttocomposeandsendanHTTPPUTrequestwiththeRESTClientincludedinPyCharmProfessional,itisnecessarytofollowthepreviouslyexplainedstepstoprovideJSONdatawithintherequestbody.

Incaseyoudon'tworkwithPyCharmProfessional,runanyofthefollowingcommandstocomposeandsendtheHTTPPOSTrequesttocreatethenewgame:

httpPOST:8000/games/name='TeenageMutantNinjaTurtles'game_category='3D

RPG'played=falserelease_date='2016-05-18T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Teenage

MutantNinjaTurtles","game_category":"3DRPG","played":"false",

"release_date":"2016-05-18T03:02:00.776594Z"}':8000/games/

TelerikFiddlerisapopulartoolforWindowsdevelopers.TelerikFiddlerisafreeWebdebuggingproxywithaGUIbutitonlyrunsonWindows.ItsmainWebpagepromotesitasamulti-platformtool,butatthetimethisbookwaspublished,themacOSandLinuxversionswerecompletelyunstableandtheirdevelopmentabandoned.WecanuseTelerikFiddlerinWindowstocomposeandsendHTTPrequests,amongotherfeatures.YoucandownloadFiddlerforWindowsfromthefollowingURL-https://www.telerik.com/download/fiddler.

StoplightisapopularpowerfulAPImodelingtoolthatallowsustoeasilytestourAPIs.ItsHTTPrequestmakerallowsustocomposeandsendrequestsandgeneratethenecessarycodetomakethemindifferentprogramminglanguages,suchasJavaScript,Swift,C#,PHP,Node,andGo,amongothers.YoucansignuptoworkwithStoplightatthefollowingURL-http://stoplight.io.

WecanalsouseappsthatcancomposeandsendHTTPrequestsfrommobiledevicestoworkwiththeRESTfulAPI.Forexample,wecanworkwiththeiCurlHTTPApponiOSdevicessuchasiPadandiPhone-https://itunes.apple.com/us/app/icurlhttp/id611943891?mt=8.InAndroiddevices,wecanworkwiththeHTTPRequestApp-https://play.google.com/store/apps/details?id=air.http.request&hl=en.

ThefollowingscreenshotshowstheresultsofcomposingandsendingthefollowingHTTPrequestwiththeiCurlHTTPApp:GEThttp://192.168.1.106:8000/games/.RememberthatyouhavetoperformthepreviouslyexplainedconfigurationsinyourLANandroutertobeabletoaccesstheDjangodevelopmentserverfromotherdevicesconnectedtoyourLAN.Inthiscase,theIPassignedtothecomputerrunningtheDjangoWebserveris192.168.1.106,andtherefore,youmustreplacethisIPwiththeIPassignedtoyourdevelopmentcomputer.

Atthetimethisbookwaspublished,themobileappsthatallowyoutocomposeandsendHTTPrequestsdonotprovideallthefeaturesyoucanfindinPostmanorcommand-lineutilities.

Page 75: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select
Page 76: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. IfwewanttocreateasimplePlayermodelthatwewillusetorepresentandpersist

playersinDjangoRESTframework,wecancreate:1. APlayerclassasasubclassofthedjangorestframework.models.Modelclass.2. APlayerclassasasubclassofthedjango.db.models.Modelclass.3. APlayerfunctionintherestframeworkmodels.pyfile.

2. IntheDjangoRESTFramework,serializersare:1. MediatorsbetweenthemodelinstancesandPythonprimitives.2. MediatorsbetweentheviewfunctionsandPythonprimitives.3. MediatorsbetweentheURLsandviewfunctions.

3. IntheDjangoRESTFramework,parsersandrenderers:1. HandleasmediatorsbetweenmodelinstancesandPythonprimitives.2. Resettheboard.3. HandleasmediatorsbetweenPythonprimitivesandHTTPrequestsandresponses.

4. Theurlpatternslistdeclaredintheurls.pyfilemakesitpossibleto:1. RouteURLstoviews.2. RouteURLstomodels.3. RouteURLstoPythonprimitives.

5. HTTPieisa:1. Command-lineHTTPserverwritteninPythonthatmakesiteasytocreatea

RESTfulWebServer.2. Command-lineutilitythatallowsustorunqueriesagainstanSQLitedatabase.3. Command-lineHTTPclientwritteninPythonthatmakesiteasytocomposeand

sendHTTPrequests.

Page 77: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wedesignedaRESTfulAPItointeractwithasimpleSQLitedatabaseandperformCRUDoperationswithgames.WedefinedtherequirementsforourAPIandweunderstoodthetasksperformedbyeachHTTPmethod.WelearnedtheadvantagesofworkingwithlightweightvirtualenvironmentsinPythonandwesetupavirtualenvironmentwithDjangoRESTFramework.

WecreatedamodeltorepresentandpersistgamesandweexecutedmigrationsinDjango.WelearnedtomanageserializationandserializationofgameinstancesintoJSONrepresentationswithDjangoRESTFramework.WewroteAPIviewstoprocessthedifferentHTTPrequestsandweconfiguredtheURLpatternslisttorouteURLstoviews.

Finally,westartedtheDjangodevelopmentserverandweusedcommand-linetoolstocomposeandsendHTTPrequeststoourRESTfulAPIandanalyzedhoweachHTTPrequestwasprocessedinourcode.WealsoworkedwithGUItoolstocomposeandsendHTTPrequests.

NowthatweunderstandthebasicsofDjangoRESTFramework,wewillexpandthecapabilitiesoftheRESTfulWebAPIbytakingadvantageoftheadvancedfeaturesincludedintheDjangoRESTFramework,whichiswhatwearegoingtodiscussinthenextchapter.

Page 78: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter2.WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjangoInthischapter,wewillexpandthecapabilitiesoftheRESTfulAPIthatwestartedinthepreviouschapter.WewillchangetheORMsettingstoworkwithamorepowerfulPostgreSQLdatabaseandwewilltakeadvantageoftheadvancedfeaturesincludedinDjangoRESTFrameworkthatallowustoreducetheboilerplatecodeforcomplexAPIs,suchasclass-basedviews.Wewill:

UsemodelserializerstoeliminateduplicatecodeWorkwithwrapperstowriteAPIviewsUsethedefaultparsingandrenderingoptionsandmovebeyondJSONBrowsetheAPIDesignaRESTfulAPItointeractwithacomplexPostgreSQLdatabaseUnderstandthetasksperformedbyeachHTTPmethodDeclarerelationshipswiththemodelsManageserializationanddeserializationwithrelationshipsandhyperlinksCreateclassbasedviewsandusegenericclassesWorkwithendpointsfortheAPICreateandretrieverelatedresources

Page 79: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UsingmodelserializerstoeliminateduplicatecodeTheGameSerializerclassdeclaresmanyattributeswiththesamenamesthatweusedintheGamemodelandrepeatsinformation,suchasthetypesandthemax_lengthvalues.TheGameSerializerclassisasubclassofrest_framework.serializers.Serializer,itdeclaresattributesthatwemanuallymappedtotheappropriatetypesandoverridesthecreateandupdatemethods.

Now,wewillcreateanewversionoftheGameSerializerclassthatwillinheritfromtherest_framework.serializers.ModelSerializerclass.TheModelSerializerclassautomaticallypopulatesbothsetofdefaultfieldsandasetofdefaultvalidators.Inaddition,theclassprovidesdefaultimplementationsforthecreateandupdatemethods.

Tip

IncaseyouhaveanyexperiencewithDjangoWebFramework,youwillnoticethattheSerializerandModelSerializerclassesaresimilartotheFormandModelFormclasses.

Now,gotothegamesapi/gamesfolderandopentheserializers.pyfile.Replacethecodeinthisfilewiththefollowingcode,thatdeclaresthenewversionoftheGameSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_01folder:

fromrest_frameworkimportserializers

fromgames.modelsimportGame

classGameSerializer(serializers.ModelSerializer):

classMeta:

model=Game

fields=('id',

'name',

'release_date',

'game_category',

'played')

ThenewGameSerializerclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,theGameclass.Thefieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesthatwewanttoincludeintheserializationfromtherelatedmodel.

Thereisnoneedtooverrideeithercreateorupdatemethodsbecausethegenericbehaviorwillbeenoughinthiscase.TheModelSerializersuperclassprovidesimplementationsforbothmethods.

Wehavereducedtheboilerplatecodethatwedidn'trequireintheGameSerializerclass.We

Page 80: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

justneededtospecifythedesiredsetoffieldsinatuple.Now,thetypesrelatedtothegamefieldsareincludedonlyintheGameclass.

Tip

PressCtrl+CtoquitDjango'sdevelopmentserverandexecutethefollowingcommandtostartitagain:

pythonmanage.pyrunserver

Page 81: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithwrapperstowriteAPIviewsOurcodeinthegames/views.pyfiledeclaredaJSONResponseclassandtwofunction-basedviews.ThesefunctionsreturnedJSONResponsewhenitwasnecessarytoreturnJSONdataandadjango.Http.Response.HttpResponseinstancewhentheresponsewasjustofanHTTPstatuscode.

NomattertheacceptedcontenttypespecifiedintheHTTPrequestheader,theviewfunctionsalwaysprovidethesamecontentintheresponsebody-JSON.RunthefollowingtwocommandstoretrieveallthegameswithdifferentvaluesfortheAcceptrequestheader-text/htmlandapplication/json:

http:8000/games/Accept:text/html

http:8000/games/Accept:application/json

Thefollowingaretheequivalentcurlcommands:

curl-H'Accept:text/html'-iXGET:8000/games/

curl-H'Accept:application/json'-iXGET:8000/games/

TheprecedingcommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/games/.Thefirstcommanddefinesthetext/htmlvaluefortheAcceptrequestheader.Thesecondcommanddefinestheapplication/jsonvaluefortheAcceptrequestheader.

Youwillnoticethatboththecommandsproducethesameresults,andtherefore,theviewfunctionsdon'ttakeintoaccountthevaluespecifiedfortheAcceptrequestheaderintheHTTPrequests.Theheaderresponseforbothcommandswillincludethefollowingline:

Content-Type:application/json

Thesecondrequestspecifiedthatitwillonlyaccepttext/htmlbuttheresponseincludedaJSONbody,thatis,application/jsoncontent.Thus,ourfirstversionoftheRESTfulAPIisnotpreparedtorendercontentotherfromJSON.WewillmakesomechangestoenabletheAPItorenderothercontents.

WheneverwehavedoubtsaboutthemethodssupportedbyaresourceorresourcecollectioninaRESTfulAPI,wecancomposeandsendanHTTPrequestwiththeOPTIONSHTTPverbandtheURLfortheresourceorresourcecollection.IftheRESTfulAPIimplementstheOPTIONSHTTPverbforaresourceorresourcecollection,itprovidesacomma-separatedlistofHTTPverbsormethodsthatitsupportsasavaluefortheAllowheaderintheresponse.Inaddition,theresponseheaderwillincludeadditionalinformationaboutothersupportedoptions,suchasthecontenttypeitiscapableofparsingfromtherequestandthecontenttypeitiscapableofrenderingontheresponse.

Forexample,ifwewanttoknowtheHTTPverbsthatthegamescollectionsupports,wecan

Page 82: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

runthefollowingcommand:

httpOPTIONS:8000/games/

Thefollowingistheequivalentcurlcommand:

curl-iXOPTIONS:8000/games/

ThepreviouscommandwillcomposeandsendthefollowingHTTPrequest:OPTIONShttp://localhost:8000/games/.Therequestwillmatchandruntheviews.game_listfunction,thatis,thegame_listfunctiondeclaredwithinthegames/views.pyfile.Thisfunctiononlyrunsthecodewhentherequest.methodisequalto'GET'or'POST'.Inthiscase,request.methodisequalto'OPTIONS',andtherefore,thefunctionwon'trunanycodeandwon'treturnanyresponse,specifically,itwon'treturnanHttpResponseinstance.Asaresult,wewillseethefollowingInternalServerErrorlistedinDjango'sdevelopmentserverconsoleoutput:

InternalServerError:/games/

Traceback(mostrecentcalllast):

File"/Users/gaston/Projects/PythonRESTfulWebAPI/Django01/lib/python3.5/site-

packages/django/core/handlers/base.py",line158,inget_response

%(callback.__module__,view_name))

ValueError:Theviewgames.views.game_listdidn'treturnanHttpResponseobject.

ItreturnedNoneinstead.

[08/Jun/201620:21:40]"OPTIONS/games/HTTP/1.1"50049173

ThefollowinglinesshowtheheaderfortheoutputthatalsoincludesadetailedHTMLdocumentwithdetailedinformationabouttheerrorbecausethedebugmodeisactivatedforDjango.Wereceivea500InternalServerErrorstatuscode:

HTTP/1.0500InternalServerError

Content-Type:text/html

Date:Wed,08Jun201620:21:40GMT

Server:WSGIServer/0.2CPython/3.5.1

X-Frame-Options:SAMEORIGIN

Obviously,wewanttoprovideamoreconsistentAPIandwewanttoprovideanaccurateresponsewhenwereceivearequestwiththeOPTIONSverbsforeitheragameresourceorthegamescollection.

IfwecomposeandsendanHTTPrequestwiththeOPTIONSverbforagameresource,wewillseethesameerrorandwewillhaveasimilarresponsebecausetheviews.game_detailfunctiononlyrunsthecodewhentherequest.methodisequalto'GET','PUT',or'DELETE'.

Thefollowingcommandswillproducetheexplainederrorwhenwetrytoseetheoptionsofferedforthegameresourcewhoseidorprimarykeyisequalto3.Don'tforgettoreplace3withaprimarykeyvalueofanexistinggameinyourconfiguration:

httpOPTIONS:8000/games/3/

Page 83: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thefollowingistheequivalentcurlcommand:

curl-iXOPTIONS:8000/games/3/

Wejustneedtomakeafewchangesinthegames/views.pyfiletosolvetheissueswehavebeenanalyzingforourRESTfulAPI.Wewillusethe@api_viewdecorator,declaredinrest_framework.decorators,forourfunction-basedviews.ThisdecoratorallowsustospecifytheHTTPverbsthatourfunctioncanprocess.IftherequestthathastobeprocessedbytheviewfunctionhasanHTTPverbthatisn'tincludedinthestringlistspecifiedasthehttp_method_namesargumentforthe@api_viewdecorator,thedefaultbehaviorreturnsa405MethodNotAllowedstatuscode.Thisway,wemakesurethatwheneverwereceiveanHTTPverbthatisn'tconsideredwithinourfunctionview,wewon'tgenerateanunexpectederrorasthedecoratorhandlestheresponsefortheunsupportedHTTPverbsormethods.

Tip

Underthehoods,the@api_viewdecoratorisawrapperthatconvertsafunction-basedviewsintoasubclassoftherest_framework.views.APIViewclass.ThisclassisthebaseclassforallviewsinDjangoRESTFramework.Aswemightguess,incasewewanttoworkwithclass-basedview,wecancreateclassesthatinheritfromthisclassandwewillhavethesamebenefitsthatweanalyzedforthefunction-basedviewsthatusethedecorator.Wewillworkwithclass-basedviewsintheforthcomingexamples.

Inaddition,aswespecifyastringlistwiththesupportedHTTPverbs,thedecoratorautomaticallybuildstheresponsefortheOPTIONSHTTPverbwiththesupportedmethodsandparserandrendercapabilities.OuractualversionoftheAPIisjustcapableofrenderingJSONasitsoutput.Theusageofthedecoratormakessurethatwealwaysreceiveaninstanceoftherest_framework.request.RequestclassintherequestargumentwhenDjangocallsourviewfunction.ThedecoratoralsohandlestheParserErrorexceptionswhenourfunctionviewsaccesstherequest.dataattributethatmightcauseparsingproblems.

Page 84: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UsingthedefaultparsingandrenderingoptionsandmovebeyondJSONTheAPIViewclassspecifiesdefaultsettingsforeachviewthatwecanoverridebyspecifyingappropriatevaluesinthegamesapi/settings.pyfileorbyoverridingtheclassattributesinsubclasses.Aspreviouslyexplained,theusageoftheAPIViewclassunderthehoodsmakesthedecoratorapplythesedefaultsettings.Thus,wheneverweusethedecorator,thedefaultparserclassesandthedefaultrendererclasseswillbeassociatedwiththefunctionviews.

Bydefault,thevaluefortheDEFAULT_PARSER_CLASSESisthefollowingtupleofclasses:

(

'rest_framework.parsers.JSONParser',

'rest_framework.parsers.FormParser',

'rest_framework.parsers.MultiPartParser'

)

Whenweusethedecorator,theAPIwillbeabletohandleanyofthefollowingcontenttypesthroughtheappropriateparserswhenaccessingtherequest.dataattribute:

application/json

application/x-www-form-urlencoded

multipart/form-data

Tip

Whenweaccesstherequest.dataattributeinthefunctions,DjangoRESTFrameworkexaminesthevaluefortheContent-Typeheaderintheincomingrequestanddeterminestheappropriateparsertoparsetherequestcontent.Ifweusethepreviouslyexplaineddefaultvalues,theDjangoRESTFrameworkwillbeabletoparsethepreviouslylistedcontenttypes.However,itisextremelyimportantthattherequestspecifiestheappropriatevalueintheContent-Typeheader.

Wehavetoremovetheusageoftherest_framework.parsers.JSONParserclassinthefunctionstomakeitpossibletobeabletoworkwithalltheconfiguredparsersandstopworkingwithaparserthatonlyworkswithJSON.Thegame_listfunctionexecutesthefollowingtwolineswhenrequest.methodisequalto'POST':

game_data=JSONParser().parse(request)

game_serializer=GameSerializer(data=game_data)

WewillremovethefirstlinethatusestheJSONParserandwewillpassrequest.dataasthedataargumentfortheGameSerializer.Thefollowinglinewillreplacethepreviouslines:

game_serializer=GameSerializer(data=request.data)

Thegame_detailfunctionexecutesthefollowingtwolineswhenrequest.methodisequalto

Page 85: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

'PUT':

game_data=JSONParser().parse(request)

game_serializer=GameSerializer(game,data=game_data)

Wewillmakethesameeditsdoneforthecodeinthegame_listfunction.WewillremovethefirstlinethatusestheJSONParserandwewillpassrequest.dataasthedataargumentfortheGameSerializer.Thefollowinglinewillreplacethepreviouslines:

game_serializer=GameSerializer(game,data=request.data)

Bydefault,thevaluefortheDEFAULT_RENDERER_CLASSESisthefollowingtupleofclasses:

(

'rest_framework.renderers.JSONRenderer',

'rest_framework.renderers.BrowsableAPIRenderer',

)

Whenweusethedecorator,theAPIwillbeabletorenderthefollowingcontenttypesintheresponse,throughtheappropriaterenderers,whenworkingwiththerest_framework.response.Responseobject:

application/json

text/html

Bydefault,thevaluefortheDEFAULT_CONTENT_NEGOTIATION_CLASSistherest_framework.negotiation.DefaultContentNegotiationclass.Whenweusethedecorator,theAPIwillusethiscontentnegotiationclasstoselecttheappropriaterendererfortheresponsebasedontheincomingrequest.Thisway,whenarequestspecifiesthatitwillaccepttext/html,thecontentnegotiationclassselectstherest_framework.renderers.BrowsableAPIRenderertorendertheresponseandgeneratetext/htmlinsteadofapplication/json.

WehavetoreplacetheusageofboththeJSONResponseandHttpResponseclassesinthefunctionswiththerest_framework.response.Responseclass.TheResponseclassusesthepreviouslyexplainedcontentnegotiationfeatures,rendersthereceiveddataintotheappropriatecontenttype,andreturnsittotheclient.

Now,gotothegamesapi/gamesfolderandopentheviews.pyfile.ReplacethecodeinthisfilewiththefollowingcodethatremovestheJSONResponseclassandusesthe@api_viewdecoratorforthefunctionsandtherest_framework.response.Responseclass.Themodifiedlinesarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_02_02folder:

fromrest_framework.parsersimportJSONParser

fromrest_frameworkimportstatus

fromrest_framework.decoratorsimportapi_view

fromrest_framework.responseimportResponse

fromgames.modelsimportGame

Page 86: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

fromgames.serializersimportGameSerializer

@api_view(['GET','POST'])

defgame_list(request):

ifrequest.method=='GET':

games=Game.objects.all()

games_serializer=GameSerializer(games,many=True)

returnResponse(games_serializer.data)

elifrequest.method=='POST':

game_serializer=GameSerializer(data=request.data)

ifgame_serializer.is_valid():

game_serializer.save()

returnResponse(game_serializer.data,

status=status.HTTP_201_CREATED)

returnResponse(game_serializer.errors,status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET','PUT','POST'])

defgame_detail(request,pk):

try:

game=Game.objects.get(pk=pk)

exceptGame.DoesNotExist:

returnResponse(status=status.HTTP_404_NOT_FOUND)

ifrequest.method=='GET':

game_serializer=GameSerializer(game)

returnResponse(game_serializer.data)

elifrequest.method=='PUT':

game_serializer=GameSerializer(game,data=request.data)

ifgame_serializer.is_valid():

game_serializer.save()

returnResponse(game_serializer.data)

returnResponse(game_serializer.errors,

status=status.HTTP_400_BAD_REQUEST)

elifrequest.method=='DELETE':

game.delete()

returnResponse(status=status.HTTP_204_NO_CONTENT)

Afteryousavetheprecedingchanges,runthefollowingcommand:

httpOPTIONS:8000/games/

Thefollowingistheequivalentcurlcommand:

curl-iXOPTIONS:8000/games/

ThepreviouscommandwillcomposeandsendthefollowingHTTPrequest:OPTIONShttp://localhost:8000/games/.Therequestwillmatchandruntheviews.game_listfunction,thatis,thegame_listfunctiondeclaredwithinthegames/views.pyfile.Weaddedthe

Page 87: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

@api_viewdecoratortothisfunction,andtherefore,itisnowcapableofdeterminingthesupportedHTTPverbs,parsing,andrenderingcapabilities.Thefollowinglinesshowtheoutput:

HTTP/1.0200OK

Allow:GET,POST,OPTIONS

Content-Type:application/json

Date:Thu,09Jun201620:24:31GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"description":"",

"name":"GameList",

"parses":[

"application/json",

"application/x-www-form-urlencoded",

"multipart/form-data"

],

"renders":[

"application/json",

"text/html"

]

}

TheresponseheaderincludesanAllowkeywithacomma-separatedlistofHTTPverbssupportedbytheresourcecollectionasitsvalue:GET,POST,OPTIONS.Asourrequestdidn'tspecifytheallowedcontenttype,thefunctionrenderedtheresponsewiththedefaultapplication/jsoncontenttype.TheresponsebodyspecifiestheContent-typethattheresourcecollectionparsesandtheContent-typethatitrenders.

RunthefollowingcommandtocomposeandsendanHTTPrequestwiththeOPTIONSverbforagameresource.Don'tforgettoreplace3withaprimarykeyvalueofanexistinggameinyourconfiguration.

httpOPTIONS:8000/games/3/

Thefollowingistheequivalentcurlcommand:

curl-iXOPTIONS:8000/games/3/

TheprecedingcommandwillcomposeandsendthefollowingHTTPrequest:OPTIONShttp://localhost:8000/games/3/.Therequestwillmatchandruntheviews.game_detailfunction,thatis,thegame_detailfunctiondeclaredwithinthegames/views.pyfile.Wealsoaddedthe@api_viewdecoratortothisfunction,andtherefore,itiscapableofdeterminingthesupportedHTTPverbs,parsing,andrenderingcapabilities.Thefollowinglinesshowtheoutput:

HTTP/1.0200OK

Allow:GET,POST,OPTIONS,PUT

Content-Type:application/json

Page 88: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Date:Thu,09Jun201621:35:58GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"description":"",

"name":"GameDetail",

"parses":[

"application/json",

"application/x-www-form-urlencoded",

"multipart/form-data"

],

"renders":[

"application/json",

"text/html"

]

}

TheresponseheaderincludesanAllowkeywithacomma-separatedlistofHTTPverbssupportedbytheresourceasitsvalue:GET,POST,OPTIONS,PUT.Theresponsebodyspecifiesthecontent-typethattheresourceparsesandthecontent-typethatitrenders,withthesamecontentsreceivedinthepreviousOPTIONSrequestappliedtoaresourcecollection,thatis,toagamescollection.

InChapter1,DevelopingRESTfulAPIswithDjango,whenwecomposedandsentPOSTandPUTcommands,wehadtousetheusethe-H"Content-Type:application/json"optiontotellcurltosendthedataspecifiedafterthe-doptionasapplication/jsoninsteadofthedefaultapplication/x-www-form-urlencoded.Now,inadditiontoapplication/json,ourAPIiscapableofparsingapplication/x-www-form-urlencodedandmultipart/form-datadataspecifiedinthePOSTandPUTrequests.Thus,wecancomposeandsendaPOSTcommandthatsendsthedataasapplication/x-www-form-urlencoded,withthechangesmadetoourAPI.

WewillcomposeandsendanHTTPrequesttocreateanewgame.Inthiscase,wewillusethe-foptionforHTTPie,thatserializesdataitemsfromthecommandlineasformfieldsandsetstheContent-Typeheaderkeytotheapplication/x-www-form-urlencodedvalue:

http-fPOST:8000/games/name='ToyStory4'game_category='3DRPG'

played=falserelease_date='2016-05-18T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand.Notethatwedon'tusethe-Hoptionandcurlwillsendthedatainthedefaultapplication/x-www-form-urlencoded:

curl-iXPOST-d'{"name":"ToyStory4","game_category":"3DRPG","played":

"false","release_date":"2016-05-18T03:02:00.776594Z"}':8000/games/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:POSThttp://localhost:8000/games/withtheContent-Typeheaderkeysettotheapplication/x-www-form-urlencodedvalueandthefollowingdata:

Page 89: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

name=Toy+Story+4&game_category=3D+RPG&played=false&release_date=2016-05-

18T03%3A02%3A00.776594Z

Therequestspecifies/games/,andtherefore,itwillmatch'^games/$'andruntheviews.game_listfunction,thatis,theupdatedgame_detailfunctiondeclaredwithinthegames/views.pyfile.AstheHTTPverbfortherequestisPOST,therequest.methodpropertyisequalto'POST',andtherefore,thefunctionwillexecutethecodethatcreatesaGameSerializerinstanceandpassesrequest.dataasthedataargumentforitscreation.Therest_framework.parsers.FormParserclasswillparsethedatareceivedintherequest,thecodecreatesanewGameand,ifthedataisvalid,itsavesthenewGame.IfthenewGamewassuccessfullypersistedinthedatabase,thefunctionreturnsanHTTP201CreatedstatuscodeandtherecentlypersistedGameserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewGameobjectintheJSONresponse:

HTTP/1.0201Created

Allow:OPTIONS,POST,GET

Content-Type:application/json

Date:Fri,10Jun201620:38:40GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"game_category":"3DRPG",

"id":20,

"name":"ToyStory4",

"played":false,

"release_date":"2016-05-18T03:02:00.776594Z"

}

Wecanrunthefollowingcommandafterwemakethechangesinthecode,toseewhathappenswhenwecomposeandsendanHTTPrequestwithanHTTPverbthatisnotsupported:

httpPUT:8000/games/

Thefollowingistheequivalentcurlcommand:

curl-iXPUT:8000/games/

ThepreviouscommandwillcomposeandsendthefollowingHTTPrequest:PUThttp://localhost:8000/games/.Therequestwillmatchandtrytoruntheviews.game_listfunction,thatis,thegame_listfunctiondeclaredwithinthegames/views.pyfile.The@api_viewdecoratorweaddedtothisfunctiondoesn'tinclude'PUT'inthestringlistwiththeallowedHTTPverbs,andtherefore,thedefaultbehaviorreturnsa405MethodNotAllowedstatuscode.Thefollowinglinesshowtheoutputalongwiththeresponsefromthepreviousrequest.AJSONcontentprovidesadetailkeywithastringvalue,whichindicatesthatthePUTmethodisnotallowed:

HTTP/1.0405MethodNotAllowed

Page 90: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Allow:GET,OPTIONS,POST

Content-Type:application/json

Date:Sat,11Jun201600:49:30GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"detail":"Method"PUT"notallowed."

}

Page 91: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BrowsingtheAPIWiththerecentedits,wemadeitpossibleforourAPItousethedefaultcontentrenderersconfiguredinDjangoRESTFramework,andtherefore,ourAPIiscapableofrenderingthetext/htmlcontent.WecantakeadvantageofthebrowsableAPI,afeatureincludedinDjangoRESTFrameworkthatgenerateshuman-friendlyHTMLoutputforeachresourcewhenevertherequestspecifiestext/htmlasthevaluefortheContent-typekeyintherequestheader.

WheneverweenteraURLforanAPIresourceinawebbrowser,thebrowserwillrequireanHTMLresponse,andtherefore,DjangoRESTFrameworkwillprovideanHTMLresponsebuiltwithBootstrap(http://getbootstrap.com).ThisresponsewillincludeasectionthatdisplaystheresourcecontentinJSON,buttonstoperformdifferentrequests,andformstosubmitdatatotheresources.AseverythinginDjangoRESTFramework,wecancustomizethetemplatesandthemesusedtogeneratethebrowsableAPI.

Openawebbrowserandenterhttp://localhost:8000/games/.ThebrowsableAPIwillcomposeandsendaGETrequestto/games/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONgameslist.ThefollowingscreenshotshowstherenderedwebpageafterenteringtheURLinawebbrowserwiththeresourcedescription-GameList:

Page 92: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Tip

IfyoudecidetobrowsetheAPIinawebbrowserrunningonanothercomputerordeviceconnectedtotheLAN,rememberthatyouhavetousethedevelopmentcomputer'sassignedIPaddressinsteadoflocalhost.Forexample,ifthecomputer'sassignedIPv4IPaddressis192.168.1.106,insteadofhttp://localhost:8000/games/,youshouldusehttp://192.168.1.106:8000/games/.Ofcourse,youcanalsousethehostnameinsteadoftheIPaddress.

Page 93: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ThebrowsableAPIusestheinformationabouttheallowedmethodsforaresourcetoprovideuswithbuttonstorunthesemethods.Attheright-handsideoftheresourcedescription,thebrowsableAPIshowsanOPTIONSbuttonandaGET drop-downbutton.TheOPTIONSbuttonallowsustomakeanOPTIONSrequestto/games/,thatis,tothecurrentresource.TheGET drop-downbuttonallowsustomakeaGETrequestto/games/again.Ifweclickonortapthedownarrow,wecanselectthejsonoptionandthebrowsableAPIwilldisplaytherawJSONresultofaGETrequestto/games/withouttheheaders.

Atthebottomoftherenderedwebpage,thebrowsableAPIprovidesussomecontroltogenerateaPOSTrequestto/games/.TheMediatypedropdownallowsustoselectbetweentheconfiguredsupportedparsersforourAPI:

application/json

application/x-www-form-urlencoded

multipart/form-data

TheContenttextboxallowsustospecifythedatatobesenttothePOSTrequestformattedasspecifiedintheMediatypedropdown.Selectapplication/jsonintheMediatypedropdownandenterthefollowingJSONcontentintheContenttextbox:

{

"name":"Chuzzle2",

"release_date":"2016-05-18T03:02:00.776594Z",

"game_category":"2Dmobile",

"played":false

}

ClickortaponPOST.ThebrowsableAPIwillcomposeandsendaPOSTrequestto/games/withthepreviouslyspecifieddataasJSON,andwewillseetheresultsofthecallinthewebbrowser.

ThefollowingscreenshotshowsawebbrowserdisplayingtheHTTPstatuscode201CreatedintheresponseandthepreviouslyexplaineddropdownandtextboxwiththePOSTbuttontoallowustocontinuecomposingandsendingPOSTrequeststo/games/:

Page 94: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,entertheURLforanexistinggameresource,suchashttp://localhost:8000/games/2/.Makesureyoureplace2withtheidorprimarykeyofanexistinggameinthepreviouslyrenderedGamesList.ThebrowsableAPIwillcomposeandsendaGETrequestto/games/2/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONdataforthegame.

ThefollowingscreenshotshowstherenderedwebpageafterenteringtheURLinawebbrowserwiththeresourcedescription-GameDetail:

Page 95: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Tip

Page 96: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ThebrowsableAPIfeatureallowsustoeasilycheckhowtheAPIworksandtocomposeandsendHTTPrequestswithdifferentmethodstoanywebbrowserthathasaccesstoourLAN.WewilltakeadvantageoftheadditionalfeaturesincludedinthebrowsableAPI,suchasHTMLformsthatallowustoeasilycreatenewresources,later,afterwebuildanewRESTfulAPIwithPythonandDjangoRESTFramework.

Page 97: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DesigningaRESTfulAPItointeractwithacomplexPostgreSQLdatabaseSofar,ourRESTfulAPIhasperformedCRUDoperationsonasingledatabasetable.Now,wewanttocreateamorecomplexRESTfulAPIwithDjangoRESTFrameworktointeractwithacomplexdatabasemodelthathastoallowustoregisterplayerscoresforplayedgamesthataregroupedintogamecategories.InourpreviousRESTfulAPI,weusedastringfieldtospecifythegamecategoryforagame.Inthiscase,wewanttobeabletoeasilyretrieveallthegamesthatbelongtoaspecificgamecategory,andtherefore,wewillhavearelationshipbetweenagameandagamecategory.

WeshouldbeabletoperformCRUDoperationsondifferentrelatedresourcesandresourcecollections.ThefollowinglistenumeratestheresourcesandthemodelnamesthatwewillusetorepresenttheminDjangoRESTFramework:

Gamecategories(GameCategorymodel)Games(Gamemodel)Players(Playermodel)Playerscores(PlayerScoremodel)

Thegamecategory(GameCategory)justrequiresaname,andweneedthefollowingdataforagame(Game):

Aforeignkeytoagamecategory(GameCategory)AnameAreleasedateAboolvalueindicatingwhetherthegamewasplayedatleastoncebyaplayerornotAtimestampwiththedateandtimeinwhichthegamewasinsertedinthedatabase

Weneedthefollowingdataforaplayer(Player):

AgendervalueAnameAtimestampwiththedateandtimeinwhichtheplayerwasinsertedinthedatabase

Weneedthefollowingdataforthescoreachievedbyaplayer(PlayerScore):

Aforeignkeytoaplayer(Player)Aforeignkeytoagame(Game)AscorevalueAdateinwhichthescorevaluewasachievedbytheplayer

Tip

WewilltakeadvantageofalltheresourcesandtheirrelationshipstoanalyzedifferentoptionsthatDjangoRESTFrameworkprovidesuswhenworkingwithrelatedresources.Insteadof

Page 98: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

buildinganAPIthatusesthesameconfigurationtodisplayrelatedresources,wewillusediverseconfigurationsthatwillallowustoselectthemostappropriateoptionsbasedontheparticularrequirementsoftheAPIsthatwearedeveloping.

Page 99: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthetasksperformedbyeachHTTPmethodThefollowingtableshowstheHTTPverbs,thescope,andthesemanticsforthemethodsthatournewAPImustsupport.EachmethodiscomposedbyanHTTPverbandascopeandallthemethodshavewell-definedmeaningsforalltheresourcesandcollections.

HTTPverb Scope Semantics

GET

Collectionofgamecategories

Retrieveallthestoredgamecategoriesinthecollection,sortedbytheirnameinascendingorder.EachgamecategorymustincludealistofURLsforeachgameresourcethatbelongstothecategory.

GETGamecategory

Retrieveasinglegamecategory.ThegamecategorymustincludealistofURLsforeachgameresourcethatbelongstothecategory.

POST

Collectionofgamecategories

Createanewgamecategoryinthecollection.

PUTGamecategory Updateanexistinggamecategory.

PATCHGamecategory Updateoneormorefieldsofanexistinggamecategory.

DELETEGamecategory Deleteanexistinggamecategory.

GETCollectionofgames

Retrieveallthestoredgamesinthecollection,sortedbytheirnameinascendingorder.Eachgamemustincludeitsgamecategorydescription.

GET Game Retrieveasinglegame.Thegamemustincludeitsgamecategorydescription.

Page 100: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

POST Collectionofgames

Createanewgameinthecollection.

PUTGamecategory Updateanexistinggame.

PATCHGamecategory Updateoneormorefieldsofanexistinggame.

DELETEGamecategory Deleteanexistinggame.

GETCollectionofplayers

Retrieveallthestoredplayersinthecollection,sortedbytheirnameinascendingorder.Eachplayermustincludealistoftheregisteredscores,sortedbyscoreindescendingorder.Thelistmustincludeallthedetailsforthescoreachievedbytheplayeranditsrelatedgame.

GET PlayerRetrieveasingleplayer.Theplayermustincludealistoftheregisteredscores,sortedbyscoreindescendingorder.Thelistmustincludeallthedetailsforthescoreachievedbytheplayeranditsrelatedgame.

POSTCollectionofplayers Createanewplayerinthecollection.

PUT Player Updateanexistingplayer.

PATCH Player Updateoneormorefieldsofanexistingplayer.

DELETE Player Deleteanexistingplayer.

GETCollectionofscores

Retrieveallthestoredscoresinthecollection,sortedbyscoreindescendingorder.Eachscoremustincludetheplayer'snamethatachievedthescoreandthegame'sname.

GET ScoreRetrieveasinglescore.Thescoremustincludetheplayer'snamethatachievedthescoreandthegame'sname.

Page 101: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

POST Collectionofscores

Createanewscoreinthecollection.Thescoremustberelatedtoanexistingplayerandanexistinggame.

PUT Score Updateanexistingscore.

PATCH Score Updateoneormorefieldsofanexistingscore.

DELETE Score Deleteanexistingscore.

WewantourAPItobeabletoupdateasinglefieldforanexistingresource,andtherefore,wewillprovideanimplementationforthePATCHmethod.ThePUTmethodismeanttoreplaceanentireresourceandthePATCHmethodismeanttoapplyadeltatoanexistingresource.Inaddition,ourRESTfulAPImustsupporttheOPTIONSmethodforalltheresourcesandcollectionofresources.

Wedon'twanttospendtimechoosingandconfiguringthemostappropriateORM,asseeninourpreviousAPI;wejustwanttofinishtheRESTfulAPIassoonaspossibletostartinteractingwithit.WewilluseallthefeaturesandreusableelementsincludedinDjangoRESTFrameworktomakeiteasytobuildourAPI.WewillworkwithaPostgreSQLdatabase.However,incaseyoudon'twanttospendtimeinstallingPostgreSQL,youcanskipthechangeswemakeinDjangoRESTFrameworkORMconfigurationandcontinueworkingwiththedefaultSQLitedatabase.

Intheprecedingtable,wehaveahugenumberofmethodsandscopes.ThefollowinglistenumeratestheURIsforeachscopementionedinthetable,where{id}hastobereplacedwiththenumericidortheprimarykeyoftheresource:

Collectionofgamecategories:/game-categories/Gamecategory:/game-category/{id}/Collectionofgames:/games/Game:/game/{id}/Collectionofplayers:/players/Player:/player/{id}/Collectionofscores:/player-scores/Score:/player-score/{id}/

Let'sconsiderthathttp://localhost:8000/istheURLfortheAPIrunningontheDjangodevelopmentserver.WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:8000/game-categories/)toretrieveallthestoredgamecategoriesinthecollection:

GEThttp://localhost:8000/game-categories/

Page 102: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DeclaringrelationshipswiththemodelsMakesureyouquittheDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+Cintheterminalorcommand-promptwindowinwhichitisrunning.Now,wewillcreatethemodelsthatwearegoingtousetorepresentandpersistthegamecategories,games,playersandscores,andtheirrelationships.Openthegames/models.pyfileandreplaceitscontentswiththefollowingcode.Thelinesthatdeclarefieldsrelatedtoothermodelsarehighlightedinthecodelisting.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder.

fromdjango.dbimportmodels

classGameCategory(models.Model):

name=models.CharField(max_length=200)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

classGame(models.Model):

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=200)

game_category=models.ForeignKey(

GameCategory,

related_name='games',

on_delete=models.CASCADE)

release_date=models.DateTimeField()

played=models.BooleanField(default=False)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

classPlayer(models.Model):

MALE='M'

FEMALE='F'

GENDER_CHOICES=(

(MALE,'Male'),

(FEMALE,'Female'),

)

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=50,blank=False,default='')

gender=models.CharField(

max_length=2,

choices=GENDER_CHOICES,

default=MALE,

Page 103: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

classPlayerScore(models.Model):

player=models.ForeignKey(

Player,

related_name='scores',

on_delete=models.CASCADE)

game=models.ForeignKey(

Game,

on_delete=models.CASCADE)

score=models.IntegerField()

score_date=models.DateTimeField()

classMeta:

#Orderbyscoredescending

ordering=('-score',)

Theprecedingcodedeclaresthefollowingfourmodels,specificallyfourclassesassubclassesofthedjango.db.models.Modelclass:

GameCategory

Game

Player

PlayerScore

Djangoautomaticallyaddsanauto-incrementintegerprimarykeycolumnnamedidwhenitcreatesthedatabasetablerelatedtoeachmodel.Wespecifiedthefieldtypes,maximumlengths,anddefaultsformanyattributes.EachclassdeclaresaMetainnerclassthatdeclaresanorderingattribute.TheMetainnerclassdeclaredwithinthePlayerScoreclassspecifies'-score'asthevalueoftheorderingtuple,withadashasaprefixofthefieldnameandorderedbyscoreindescendingorder,insteadofthedefaultascendingorder.

TheGameCategory,Game,andPlayerclassesdeclarethe__str__methodthatreturnsthecontentsofthenameattributethatprovidesthenameortitleforeachofthesemodels.So,Djangowillcallthismethodwheneverithastoprovideahuman-readablerepresentationforthemodel.

TheGamemodeldeclaresthegame_categoryfieldwiththefollowingline:

game_category=models.ForeignKey(

GameCategory,

related_name='games',

on_delete=models.CASCADE)

Page 104: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Theprecedinglineusesthedjango.db.models.ForeignKeyclasstoprovideamany-to-onerelationshiptotheGameCategorymodel.The'games'valuespecifiedfortherelated_nameargumentcreatesabackwardsrelationfromtheGameCategorymodeltotheGamemodel.ThisvalueindicatesthenametobeusedfortherelationfromtherelatedGameCategoryobjectbacktoaGameobject.Now,wewillbeabletoaccessallthegamesthatbelongtoaspecificgamecategory.Wheneverwedeleteagamecategory,wewantallthegamesthatbelongtothiscategorytobedeletedtoo,andtherefore,wespecifiedthemodels.CASCADEvaluefortheon_deleteargument.

ThePlayerScoremodeldeclarestheplayerfieldwiththefollowingline:

player=models.ForeignKey(

Player,

related_name='scores',

on_delete=models.CASCADE)

Theprecedinglineusesthedjango.db.models.ForeignKeyclasstoprovideamany-to-onerelationshiptothePlayermodel.The'scores'valuespecifiedfortherelated_nameargumentcreatesabackwardsrelationfromthePlayermodeltothePlayerScoremodel.ThisvalueindicatesthenametobeusedfortherelationfromtherelatedPlayerobjectbacktoaPlayerScoreobject.Now,wewillbeabletoaccessallthescoresarchivebyaspecificplayer.Wheneverwedeleteaplayer,wewantallthescoresachievedbythisplayertobedeletedtoo,andtherefore,wespecifiedthemodels.CASCADEvaluefortheon_deleteargument.

ThePlayerScoremodeldeclaresthegamefieldwiththefollowingline:

game=models.ForeignKey(

Game,

on_delete=models.CASCADE)

Theprecedinglineusesthedjango.db.models.ForeignKeyclasstoprovideamany-to-onerelationshiptotheGamemodel.Inthiscase,wedon'tcreateabackwardsrelationbecausewedon'tneedit.Thus,wedon'tspecifyavaluefortherelated_nameargument.Wheneverwedeleteagame,wewantalltheregisteredscoresforthisgametobedeletedtoo,andtherefore,wespecifiedthemodels.CASCADEvaluefortheon_deleteargument.

Incaseyoucreatedanewvirtualenvironmenttoworkwiththisexampleoryoudownloadedthesamplecodeforthebook,youdon'tneedtodeleteanyexistingdatabase.However,incaseyouaremakingchangestothecodeforourpreviousAPIexample,youhavetodeletethegamesapi/db.sqlite3fileandthegames/migrationsfolder.

Then,itisnecessarytocreatetheinitialmigrationforthenewmodelswerecentlycoded.WejustneedtorunthefollowingPythonscriptsandwewillalsosynchronizethedatabaseforthefirsttime.AswelearnedfromourpreviousexampleAPI,bydefault,DjangousesanSQLitedatabase.Inthisexample,wewillbeworkingwithaPostgreSQLdatabase.However,incaseyouwanttouseSQLite,youcanskipthestepsrelatedtoPostgreSQL,itsconfigurationinDjango,andjumptothemigrationsgenerationcommand.

Page 105: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

YouwillhavetodownloadandinstallaPostgreSQLdatabaseincaseyouaren'talreadyrunningitinyourcomputerorinadevelopmentserver.Youcandownloadandinstallthisdatabasemanagementsystemfromitswebpage-http://www.postgresql.org.IncaseyouareworkingwithmacOS,Postgres.appprovidesaneasywaytoinstallandusePostgreSQLonthisoperatingsystem-http://postgresapp.com.

Tip

YouhavetomakesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable.Youshouldbeabletoexecutethepsqlcommand-lineutilityfromyourcurrentterminalorcommandprompt.Incasethefolderisn'tincludedinthePATH,youwillreceiveanerrorindicatingthatthepg_configfilecannotbefoundwhentryingtoinstallthepsycopg2package.Inaddition,youwillhavetousethefullpathtoeachofthePostgreSQLcommand-linetoolswewilluseinthesubsequentsteps.

WewillusethePostgreSQLcommand-linetoolstocreateanewdatabasenamedgames.IncaseyoualreadyhaveaPostgreSQLdatabasewiththisname,makesurethatyouuseanothernameinallthecommandsandconfigurations.YoucanperformthesametaskwithanyPostgreSQLGUItool.IncaseyouaredevelopingonLinux,itisnecessarytorunthecommandsasthepostgresuser.RunthefollowingcommandinmacOSorWindowstocreateanewdatabasenamedgames.Notethatthecommandwon'tproduceanyoutput:

createdbgames

InLinux,runthefollowingcommandtousethepostgresuser:

sudo-upostgrescreatedbgames

Now,wewillusethepsqlcommand-linetooltorunsomeSQLstatementstocreateaspecificuserthatwewilluseinDjangoandassignthenecessaryrolesforit.InmacOSorWindows,runthefollowingcommandtolaunchpsql:

psql

InmacOS,youmightneedtorunthefollowingcommandtolaunchpsqlwiththepostgresincasethepreviouscommanddoesn'twork,asitwilldependonthewayinwhichyouinstalledPostgreSQL:

sudo-upostgrespsql

InLinux,runthefollowingcommandtousethepostgresuser.

sudo-upsql

Then,runthefollowingSQLstatementsandfinallyenter\qtoexitthepsqlcommand-linetool.Replaceuser_namewithyourdesiredusernametouseinthenewdatabaseandpasswordwithyourchosenpassword.WewillusetheusernameandpasswordintheDjangoconfiguration.Youdon'tneedtorunthestepsifyouarealreadyworkingwithaspecificuser

Page 106: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

inPostgreSQLandyouhavealreadygrantedprivilegestothedatabasefortheuser:

CREATEROLEuser_nameWITHLOGINPASSWORD'password';

GRANTALLPRIVILEGESONDATABASEgamesTOuser_name;

ALTERUSERuser_nameCREATEDB;

\q

ThedefaultSQLitedatabaseengineandthedatabasefilenamearespecifiedinthegamesapi/settings.pyPythonfile.IncaseyoudecidetoworkwithPostgreSQLinsteadofSQLiteforthisexample,replacethedeclarationoftheDATABASESdictionarywiththefollowinglines.Thenesteddictionarymapsthedatabasenameddefaultwiththedjango.db.backends.postgresqldatabaseengine,thedesireddatabasename,anditssettings.Inthiscase,wewillcreateadatabasenamedgames.Makesureyouspecifythedesireddatabasenameinthevalueforthe'NAME'keyandthatyouconfiguretheuser,password,host,andportbasedonyourPostgreSQLconfiguration.Incaseyoufollowedtheprevioussteps,usethesettingsspecifiedinthesesteps:

DATABASES={

'default':{

'ENGINE':'django.db.backends.postgresql',

#Replacegameswithyourdesireddatabasename

'NAME':'games',

#Replaceusernamewithyourdesiredusername

'USER':'user_name',

#Replacepasswordwithyourdesiredpassword

'PASSWORD':'password',

#Replace127.0.0.1withthePostgreSQLhost

'HOST':'127.0.0.1',

#Replace5432withthePostgreSQLconfiguredport

#incaseyouaren'tusingthedefaultport

'PORT':'5432',

}

}

IncaseyoudecidedtousePostgreSQL,aftermakingtheprecedingchanges,itisnecessarytoinstallthePsycopg2package(psycopg2).ThispackageisaPython-PostgreSQLDatabaseAdapterandDjangousesittointeractwithaPostgreSQLdatabase.

InmacOSinstallations,wehavetomakesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable.Forexample,incasethepathtothebinfolderis/Applications/Postgres.app/Contents/Versions/latest/bin,wemustexecutethefollowingcommandtoaddthisfoldertothePATHenvironmentalvariable:

exportPATH=$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin

OncewehavemadesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable,wejustneedtorunthefollowingcommandtoinstallthispackage:

pipinstallpsycopg2

Thelastlinesoftheoutputwillindicatethatthepsycopg2packagehasbeensuccessfully

Page 107: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

installed:

Collectingpsycopg2

Installingcollectedpackages:psycopg2

Runningsetup.pyinstallforpsycopg2

Successfullyinstalledpsycopg2-2.6.2

Now,runthefollowingPythonscripttogeneratethemigrationsthatwillallowustosynchronizethedatabaseforthefirsttime:

pythonmanage.pymakemigrationsgames

Thefollowinglinesshowtheoutputgeneratedafterrunningthepreviouscommand:

Migrationsfor'games':

0001_initial.py:

-CreatemodelGame

-CreatemodelGameCategory

-CreatemodelPlayer

-CreatemodelPlayerScore

-Addfieldgame_categorytogame

Theoutputindicatesthatthegamesapi/games/migrations/0001_initial.pyfileincludesthecodetocreatetheGame,GameCategory,Player,andPlayerScoremodels.ThefollowinglinesshowthecodeforthisfilethatwasautomaticallygeneratedbyDjango.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

#-*-coding:utf-8-*-

#GeneratedbyDjango1.9.7on2016-06-1720:39

from__future__importunicode_literals

fromdjango.dbimportmigrations,models

importdjango.db.models.deletion

classMigration(migrations.Migration):

initial=True

dependencies=[

]

operations=[

migrations.CreateModel(

name='Game',

fields=[

('id',models.AutoField(auto_created=True,primary_key=True,

serialize=False,verbose_name='ID')),

('created',models.DateTimeField(auto_now_add=True)),

('name',models.CharField(max_length=200)),

('release_date',models.DateTimeField()),

('played',models.BooleanField(default=False)),

],

options={

Page 108: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

'ordering':('name',),

},

),

migrations.CreateModel(

name='GameCategory',

fields=[

('id',models.AutoField(auto_created=True,primary_key=True,

serialize=False,verbose_name='ID')),

('name',models.CharField(max_length=200)),

],

options={

'ordering':('name',),

},

),

migrations.CreateModel(

name='Player',

fields=[

('id',models.AutoField(auto_created=True,primary_key=True,

serialize=False,verbose_name='ID')),

('created',models.DateTimeField(auto_now_add=True)),

('name',models.CharField(default='',max_length=50)),

('gender',models.CharField(choices=[('M','Male'),('F',

'Female')],default='M',max_length=2)),

],

options={

'ordering':('name',),

},

),

migrations.CreateModel(

name='PlayerScore',

fields=[

('id',models.AutoField(auto_created=True,primary_key=True,

serialize=False,verbose_name='ID')),

('score',models.IntegerField()),

('score_date',models.DateTimeField()),

('game',

models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,to='games.Game')),

('player',

models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,

related_name='scores',to='games.Player')),

],

options={

'ordering':('-score',),

},

),

migrations.AddField(

model_name='game',

name='game_category',

field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,

related_name='games',to='games.GameCategory'),

),

]

Theprecedingcodedefinesasubclassofthedjango.db.migrations.MigrationclassnamedMigrationthatdefinesanoperationslistwithmanymigrations.CreateModel.Each

Page 109: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

migrations.CreateModelwillcreatethetableforeachoftherelatedmodels.NotethatDjangohasautomaticallyaddedanidfieldforeachofthemodels.Theoperationsareexecutedinthesameorderinwhichtheyappearinthelist.ThecodecreatesGame,GameCategory,Player,PlayerScore,andfinallyaddsthegame_categoryfieldtoGamewiththeforeignkeytoGameCategorybecauseitcreatedtheGamemodelbeforetheGameCategorymodel.ThecodecreatestheforeignkeysforPlayerScorewhenitcreatesthemodel:

Now,runthefollowingPythonscripttoapplyallthegeneratedmigrations.

pythonmanage.pymigrate

Thefollowinglinesshowtheoutputgeneratedafterrunningthepreviouscommand:

Operationstoperform:

Applyallmigrations:sessions,contenttypes,games,admin,auth

Runningmigrations:

Renderingmodelstates...DONE

Applyingcontenttypes.0001_initial...OK

Applyingauth.0001_initial...OK

Applyingadmin.0001_initial...OK

Applyingadmin.0002_logentry_remove_auto_add...OK

Applyingcontenttypes.0002_remove_content_type_name...OK

Applyingauth.0002_alter_permission_name_max_length...OK

Applyingauth.0003_alter_user_email_max_length...OK

Applyingauth.0004_alter_user_username_opts...OK

Applyingauth.0005_alter_user_last_login_null...OK

Applyingauth.0006_require_contenttypes_0002...OK

Applyingauth.0007_alter_validators_add_error_messages...OK

Applyinggames.0001_initial...OK

Applyingsessions.0001_initial...OK

Afterwerunthepreviouscommand,wecanusethePostgreSQLcommandlineoranyotherapplicationthatallowsustoeasilycheckthecontentsofthePostreSQLdatabasetocheckthetablesthatDjangogenerated.IncaseyouareworkingwithSQLite,wehavealreadylearnedhowtocheckthetablesinChapter1,DevelopingRESTfulAPIswithDjango.

Runthefollowingcommandtolistthegeneratedtables:

psql--username=user_name--dbname=games--command="\dt"

Thefollowinglinesshowtheoutputwithallthegeneratedtablenames:

Listofrelations

Schema|Name|Type|Owner

--------+----------------------------+-------+-----------

public|auth_group|table|user_name

public|auth_group_permissions|table|user_name

public|auth_permission|table|user_name

public|auth_user|table|user_name

public|auth_user_groups|table|user_name

public|auth_user_user_permissions|table|user_name

public|django_admin_log|table|user_name

Page 110: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

public|django_content_type|table|user_name

public|django_migrations|table|user_name

public|django_session|table|user_name

public|games_game|table|user_name

public|games_gamecategory|table|user_name

public|games_player|table|user_name

public|games_playerscore|table|user_name

(14rows)

Asseeninourpreviousexample,Djangousesthegames_prefixforthefollowingfourtablenamesrelatedtothegamesapplication.Django'sintegratedORMgeneratedthesetablesandtheforeignkeys,basedontheinformationincludedinourmodels:

games_game:PersiststheGamemodelgames_gamecategory:PersiststheGameCategorymodelgames_player:PersiststhePlayermodelgames_playerscore:PersiststhePlayerScoremodel

ThefollowingcommandwillallowyoutocheckthecontentsofthefourtablesafterwecomposeandsendHTTPrequeststotheRESTfulAPIandmakeCRUDoperationstothefourtables.ThecommandsassumethatyouarerunningPostgreSQLonthesamecomputerinwhichyouarerunningthecommand.

psql--username=user_name--dbname=games--command="SELECT*FROM

games_gamecategory;"

psql--username=user_name--dbname=games--command="SELECT*FROM

games_game;"

psql--username=user_name--dbname=games--command="SELECT*FROM

games_player;"

psql--username=user_name--dbname=games--command="SELECT*FROM

games_playerscore;"

Tip

InsteadofworkingwiththePostgreSQLcommand-lineutility,youcanuseaGUItooltocheckthecontentsofthePostgreSQLdatabase.YoucanalsousethedatabasetoolsincludedinyourfavoriteIDEtocheckthecontentsfortheSQLitedatabase.

Djangogeneratesadditionaltablesthatitrequirestosupportthewebframeworkandtheauthenticationfeaturesthatwewilluselater.

Page 111: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ManagingserializationanddeserializationwithrelationshipsandhyperlinksOurnewRESTfulWebAPIhastobeabletoserializeanddeserializetheGameCategory,Game,Player,andPlayerScoreinstancesintoJSONrepresentations.Inthiscase,wealsohavetopayspecialattentiontotherelationshipsbetweenthedifferentmodelswhenwecreatetheserializerclassestomanageserializationtoJSONanddeserializationfromJSON.

InourlastversionofthepreviousAPI,wecreatedasubclassoftherest_framework.serializers.ModelSerializerclasstomakeiteasiertogenerateaserializerandreduceboilerplatecode.Inthiscase,wewillalsodeclareaclassthatinheritsfromModelSerializer,buttheotherclasseswillinheritfromtherest_framework.serializers.HyperlinkedModelSerializerclass.

TheHyperlinkedModelSerializerisatypeofModelSerializerthatuseshyperlinkedrelationshipsinsteadofprimarykeyrelationships,andtherefore,itrepresentstherealationshipstoothermodelinstanceswithhyperlinksinsteadofprimarykeyvalues.Inaddition,theHyperlinkedModelSerializergeneratedafieldnamedurlwiththeURLfortheresourceasitsvalue.AsseeninthecaseofModelSerializer,theHyperlinkedModelSerializerclassprovidesdefaultimplementationsforthecreateandupdatemethods.

Now,gotothegamesapi/gamesfolderandopentheserializers.pyfile.ReplacethecodeinthisfilewiththefollowingcodethatdeclarestherequiredimportsandtheGameCategorySerializerclass.Wewilladdmoreclassestothisfilelater.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

fromrest_frameworkimportserializers

fromgames.modelsimportGameCategory

fromgames.modelsimportGame

fromgames.modelsimportPlayer

fromgames.modelsimportPlayerScore

importgames.views

classGameCategorySerializer(serializers.HyperlinkedModelSerializer):

games=serializers.HyperlinkedRelatedField(

many=True,

read_only=True,

view_name='game-detail')

classMeta:

model=GameCategory

fields=(

'url',

'pk',

'name',

'games')

Page 112: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TheGameCategorySerializerclassisasubclassoftheHyperlinkedModelSerializerclass.TheGameCategorySerializerclassdeclaresagamesattributeasaninstanceofserializers.HyperlinkedRelatedFieldwithmanyandread_onlyequaltoTruebecauseitisaone-to-manyrelationshipanditisread-only.Weusethegamesnamethatwespecifiedastherelated_namestringvaluewhenwecreatedthegame_categoryfieldasamodels.ForeignKeyinstanceintheGamemodel.Thisway,thegamesfieldwillprovideuswithanarrayofhyperlinkstoeachgamethatbelongtothegamecategory.Theview_namevalueis'game-detail'becausewewantthebrowsableAPIfeaturetousethegamedetailviewtorenderthehyperlinkwhentheuserclicksortapsonit.

TheGameCategorySerializerclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,theGameCategoryclass.Thefieldsattributespecifiesatupleofstringwhosevaluesindicatesthefieldnamesthatwewanttoincludeintheserializationfromtherelatedmodel.WewanttoincludeboththeprimarykeyandtheURL,andtherefore,thecodespecifiedboth'pk'and'url'asmembersofthetuple.Thereisnoneedtooverrideeitherthecreate,orupdatemethodbecausethegenericbehaviorwillbeenoughinthiscase.TheHyperlinkedModelSerializersuperclassprovidesimplementationsforbothmethods.

Now,addthefollowingcodetotheserializers.pyfiletodeclaretheGameSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

classGameSerializer(serializers.HyperlinkedModelSerializer):

#Wewanttodisplaythegamecagory'snameinsteadoftheid

game_category=

serializers.SlugRelatedField(queryset=GameCategory.objects.all(),

slug_field='name')

classMeta:

model=Game

fields=(

'url',

'game_category',

'name',

'release_date',

'played')

TheGameSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.TheGameSerializerclassdeclaresagame_categoryattributeasaninstanceofserializers.SlugRelatedFieldwithitsquerysetargumentsettoGameCategory.objects.all()anditsslug_fieldargumentsetto'name'.ASlugRelatedFieldisaread-writefieldthatrepresentsthetargetoftherelationshipbyauniqueslugattribute,thatis,thedescription.Wecreatedthegame_categoryfieldasamodels.ForeignKeyinstanceintheGamemodelandwewanttodisplaythegamecategory'snameasthedescription(slugfield)fortherelatedGameCategory.Thus,wespecified'name'astheslug_field.IncaseitisnecessarytodisplaythepossibleoptionsfortherelatedgamecategoryinaforminthebrowsableAPI,Djangowillusetheexpressionspecifiedinthequerysetargumenttoretrieveallthepossibleinstancesanddisplaytheirspecifiedslugfield.

Page 113: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TheGameCategorySerializerclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,theGameclass.Thefieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesthatwewanttoincludeintheserializationfromtherelatedmodel.WejustwanttoincludetheURL,andtherefore,thecodespecifiedboth'url'asamemberofthetuple.Thegame_categoryfieldwillspecifythenamefieldfortherelatedGameCategory.

Now,addthefollowingcodetotheserializers.pyfiletodeclaretheScoreSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

classScoreSerializer(serializers.HyperlinkedModelSerializer):

#Wewanttodisplayallthedetailsforthegame

game=GameSerializer()

#Wedon'tincludetheplayerbecauseitwillbenestedintheplayer

classMeta:

model=PlayerScore

fields=(

'url',

'pk',

'score',

'score_date',

'game',

)

TheScoreSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.WewillusetheScoreSerializerclasstoserializePlayerScoreinstancesrelatedtoaPlayer,thatis,todisplayallthescoresforaspecificplayerwhenweserializeaPlayer.WewanttodisplayallthedetailsfortherelatedGamebutwedon'tincludetherelatedPlayerbecausethePlayerwillusethisScoreSerializerserializer.

TheScoreSerializerclassdeclaresagameattributeasaninstanceofthepreviouslycodedGameSerializerclass.Wecreatedthegamefieldasamodels.ForeignKeyinstanceinthePlayerScoremodelandwewanttoserializethesamedataforthegamethatwecodedintheGameSerializerclass.

TheScoreSerializerclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,thePlayerScoreclass.Aspreviouslyexplain,wedon'tincludethe'player'fieldnameinthefieldstupleofstringtoavoidserializingtheplayeragain.WewilluseaPlayerSerializerasamasterandtheScoreSerializerasthedetail.

Now,addthefollowingcodetotheserializers.pyfiletodeclarethePlayerSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

classPlayerSerializer(serializers.HyperlinkedModelSerializer):

scores=ScoreSerializer(many=True,read_only=True)

gender=serializers.ChoiceField(

choices=Player.GENDER_CHOICES)

gender_description=serializers.CharField(

Page 114: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

source='get_gender_display',

read_only=True)

classMeta:

model=Player

fields=(

'url',

'name',

'gender',

'gender_description',

'scores',

)

ThePlayerSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.WewillusethePlayerSerializerclasstoserializePlayerinstancesandwewillusethepreviouslydeclaredScoreSerializerclasstoserializeallthePlayerScoreinstancesrelatedtothePlayer.

ThePlayerSerializerclassdeclaresascoresattributeasaninstanceofthepreviouslycodedScoreSerializerclass.ThemanyargumentissettoTruebecauseitisaone-to-manyrelationship.Weusethescoresnamethatwespecifiedastherelated_namestringvaluewhenwecreatedtheplayerfieldasamodels.ForeignKeyinstanceinthePlayerScoremodel.Thisway,thescoresfieldwillrendereachPlayerScorethatbelongstothePlayerusingthepreviouslydeclaredScoreSerializer.

ThePlayermodeldeclaredgenderasaninstanceofmodels.CharFieldwiththechoicesattributesettothePlayer.GENDER_CHOICESstringtuple.TheScoreSerializerclassdeclaresagenderattributeasaninstanceofserializers.ChoiceFieldwiththechoicesargumentsettothePlayer.GENDER_CHOICESstringtuple.Inaddition,theclassdeclaresagender_descriptionattributewithread_onlysettoTrueandthesourceargumentsetto'get_gender_display'.Thesourcestringisbuiltwithget_followedbythefieldname,gender,and_display.Thisway,theread-onlygender_descriptionattributewillrenderthedescriptionforthegenderchoicesinsteadofthesinglecharstoredvalues.

TheScoreSerializerclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,thePlayerScoreclass.Aspreviouslyexplained,wedon'tincludethe'player'fieldnameinthefieldstupleofstringtoavoidserializingtheplayeragain.WewilluseaPlayerSerializerasamasterandtheScoreSerializerasthedetail.

Finally,addthefollowingcodetotheserializers.pyfiletodeclarethePlayerScoreSerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

classPlayerScoreSerializer(serializers.ModelSerializer):

player=serializers.SlugRelatedField(queryset=Player.objects.all(),

slug_field='name')

#Wewanttodisplaythegame'snameinsteadoftheid

game=serializers.SlugRelatedField(queryset=Game.objects.all(),

Page 115: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

slug_field='name')

classMeta:

model=PlayerScore

fields=(

'url',

'pk',

'score',

'score_date',

'player',

'game',

)

ThePlayerScoreSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.WewillusethePlayerScoreSerializerclasstoserializePlayerScoreinstances.Previously,wecreatedtheScoreSerializerclasstoserializePlayerScoreinstancesasthedetailofaplayer.WewillusethenewPlayerScoreSerializerclasswhenwewanttodisplaytherelatedplayer'snameandtherelatedgame'sname.Intheotherserializerclass,wedidn'tincludeanyinformationrelatedtotheplayerandweincludedallthedetailsforthegame.

ThePlayerScoreSerializerclassdeclaresaplayerattributeasaninstanceofserializers.SlugRelatedFieldwithitsquerysetargumentsettoPlayer.objects.all()anditsslug_fieldargumentsetto'name'.Wecreatedtheplayerfieldasamodels.ForeignKeyinstanceinthePlayerScoremodelandwewanttodisplaytheplayer'snameasthedescription(slugfield)fortherelatedPlayer.Thus,wespecified'name'astheslug_field.IncaseitisnecessarytodisplaythepossibleoptionsfortherelatedgamecategoryinaforminthebrowsableAPI,Djangowillusetheexpressionspecifiedinthequerysetargumenttoretrieveallthepossibleplayersanddisplaytheirspecifiedslugfield.

ThePlayerScoreSerializerclassdeclaresagameattributeasaninstanceofserializers.SlugRelatedFieldwithitsquerysetargumentsettoGame.objects.all()anditsslug_fieldargumentsetto'name'.Wecreatedthegamefieldasamodels.ForeignKeyinstanceinthePlayerScoremodelandwewanttodisplaythegame'snameasthedescription(slugfield)fortherelatedGame.

Page 116: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Creatingclass-basedviewsandusinggenericclassesThistime,wewillwriteourAPIviewsbydeclaringclass-basedviews,insteadoffunction-basedviews.Wemightcodeclassesthatinheritfromtherest_framework.views.APIViewclassanddeclaremethodswiththesamenamesthantheHTTPverbswewanttoprocess:get,post,put,patch,delete,andsoon.Thesemethodsreceivearequestargumentashappenedwiththefunctionsthatwecreatedfortheviews.However,thisapproachwouldrequireustowritealotofcode.Instead,wecantakeadvantageofasetofgenericviewsthatwecanuseasourbaseclassesforourclass-basedviewstoreducetherequiredcodetotheminimumandtakeadvantageofthebehaviorthathasbeengeneralizedinDjangoRESTFramework.

Wewillcreatesubclassesofthetwofollowinggenericclassviewsdeclaredinrest_framework.generics:

ListCreateAPIView:Implementsthegetmethodthatretrievesalistingofaquerysetandthepostmethodthatcreatesamodelinstance.RetrieveUpdateDestroyAPIView:Implementstheget,put,patch,anddeletemethodstoretreive,completelyupdate,partiallyupdateordeleteamodelinstance.

ThosetwogenericviewsarecomposedbycombiningreusablebitsofbehaviorinDjangoRESTFrameworkimplementedasmixinclassesdeclaredinrest_framework.mixins.Wecancreateaclassthatusesmultipleinheritanceandcombinethefeaturesprovidedbymanyofthesemixinclasses.ThefollowinglineshowsthedeclarationoftheListCreateAPIViewclassasthecompositionofListModelMixin,CreateModelMixinandrest_framework.generics.GenericAPIView:

classListCreateAPIView(mixins.ListModelMixin,

mixins.CreateModelMixin,

GenericAPIView):

ThefollowinglineshowsthedeclarationoftheRetrieveUpdateDestroyAPIViewclassasthecompositionofRetrieveModelMixin,UpdateModelMixin,DestroyModelMixinandrest_framework.generics.GenericAPIView:

classRetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,

mixins.UpdateModelMixin,

mixins.DestroyModelMixin,

GenericAPIView):

Now,wewillcreateaDjangoclassbasedviewsthatwillusethepreviouslyexplainedgenericclassesandtheserializerclassestoreturnJSONrepresentationsforeachHTTPrequestthatourAPIwillhandle.Wewilljusthavetospecifyaquerysetthatretrievesalltheobjectsinthequerysetattributeandtheserializerclassintheserializer_classattributeforeachsubclassthatwedeclare.Thegenericclasseswilldotherestforus.Inaddition,wewilldeclareanameattributewiththestringnamewewillusetoidentifytheview.

Page 117: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TakingadvantageofgenericclassbasedviewsGotothegamesapi/gamesfolderandopentheviews.pyfile.Replacethecodeinthisfilewiththefollowingcodethatdeclarestherequiredimportsandtheclassbasedviews.Wewilladdmoreclassestothisfilelater.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

fromgames.modelsimportGameCategory

fromgames.modelsimportGame

fromgames.modelsimportPlayer

fromgames.modelsimportPlayerScore

fromgames.serializersimportGameCategorySerializer

fromgames.serializersimportGameSerializer

fromgames.serializersimportPlayerSerializer

fromgames.serializersimportPlayerScoreSerializer

fromrest_frameworkimportgenerics

fromrest_framework.responseimportResponse

fromrest_framework.reverseimportreverse

classGameCategoryList(generics.ListCreateAPIView):

queryset=GameCategory.objects.all()

serializer_class=GameCategorySerializer

name='gamecategory-list'

classGameCategoryDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=GameCategory.objects.all()

serializer_class=GameCategorySerializer

name='gamecategory-detail'

classGameList(generics.ListCreateAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-list'

classGameDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-detail'

classPlayerList(generics.ListCreateAPIView):

queryset=Player.objects.all()

serializer_class=PlayerSerializer

name='player-list'

classPlayerDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=Player.objects.all()

serializer_class=PlayerSerializer

name='player-detail'

Page 118: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

classPlayerScoreList(generics.ListCreateAPIView):

queryset=PlayerScore.objects.all()

serializer_class=PlayerScoreSerializer

name='playerscore-list'

classPlayerScoreDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=PlayerScore.objects.all()

serializer_class=PlayerScoreSerializer

name='playerscore-detail'

Thefollowingtablesummarizesthemethodsthateachclass-basedviewisgoingtoprocess:

Scope Classbasedviewname

HTTPverbsthatitwillprocess

Collectionofgamecategories-/game-categories/

GameCategoryList GETandPOST

Gamecategory-/game-category/{id}/ GameCategoryDetail GET,PUT,PATCHandDELETE

Collectionofgames-/games/ GameList GETandPOST

Game-/game/{id}/ GameDetail GET,PUT,PATCHandDELETE

Collectionofplayers-/players/ PlayerList GETandPOST

Player-/player/{id}/ PlayerDetail GET,PUT,PATCHandDELETE

Collectionofscores-/player-scores/ PlayerScoreList GETandPOST

Score-/player-score/{id}/ PlayerScoreDetail GET,PUT,PATCHandDELETE

Inaddition,wewillbeabletoexecutetheOPTIONSHTTPverbonanyofthescopes.

Page 119: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithendpointsfortheAPIWewanttocreateanendpointfortherootofourAPItomakeiteasiertobrowsetheAPIwiththebrowsableAPIfeatureandunderstandhoweverythingworks.Addthefollowingcodetotheviews.pyfiletodeclaretheApiRootclass.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder.

classApiRoot(generics.GenericAPIView):

name='api-root'

defget(self,request,*args,**kwargs):

returnResponse({

'players':reverse(PlayerList.name,request=request),

'game-categories':reverse(GameCategoryList.name,request=request),

'games':reverse(GameList.name,request=request),

'scores':reverse(PlayerScoreList.name,request=request)

})

TheApiRootclassisasubclassoftherest_framework.generics.GenericAPIViewclassanddeclaresthegetmethod.TheGenericAPIViewclassisthebaseclassforalltheothergenericviews.TheApiRootclassdefinesthegetmethodthatreturnsaResponseobjectwithkey-valuepairsofstringthatprovideadescriptivenamefortheviewanditsURL,generatedwiththerest_framework.reverse.reversefunction.ThisURLresolverfunctionreturnsafullyqualifiedURLfortheview.

Gotothegamesapi/gamesfolderandopentheurls.pyfile.Replacethecodeinthisfilewiththefollowingcode.ThefollowinglinesshowthecodeforthisfilethatdefinestheURLpatternsthatspecifiestheregularexpressionsthathavetobematchedintherequesttorunaspecificmethodforaclass-basedviewdefinedintheviews.pyfile.Insteadofspecifyingafunctionthatrepresentsaviewwecalltheas_viewmethodfortheclass-basedview.Weusetheas_viewmethod.Thecodefileforthesampleisincludedintherestful_python_chapter_02_03folder:

fromdjango.conf.urlsimporturl

fromgamesimportviews

urlpatterns=[

url(r'^game-categories/$',

views.GameCategoryList.as_view(),

name=views.GameCategoryList.name),

url(r'^game-categories/(?P<pk>[0-9]+)/$',

views.GameCategoryDetail.as_view(),

name=views.GameCategoryDetail.name),

url(r'^games/$',

views.GameList.as_view(),

name=views.GameList.name),

url(r'^games/(?P<pk>[0-9]+)/$',

views.GameDetail.as_view(),

name=views.GameDetail.name),

url(r'^players/$',

Page 120: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

views.PlayerList.as_view(),

name=views.PlayerList.name),

url(r'^players/(?P<pk>[0-9]+)/$',

views.PlayerDetail.as_view(),

name=views.PlayerDetail.name),

url(r'^player-scores/$',

views.PlayerScoreList.as_view(),

name=views.PlayerScoreList.name),

url(r'^player-scores/(?P<pk>[0-9]+)/$',

views.PlayerScoreDetail.as_view(),

name=views.PlayerScoreDetail.name),

url(r'^$',

views.ApiRoot.as_view(),

name=views.ApiRoot.name),

]

WhenwecodedourpreviousversionoftheAPI,wereplacedthecodeintheurls.pyfileinthegamesapifolder,specifically,thegamesapi/urls.pyfile.WemadethenecessarychangestodefinetherootURLconfigurationandincludetheURLpatterndeclaredinthepreviouslycodedgames/urls.pyfile.

Now,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequeststoourstillunsecure,yetmuchmorecomplexWebAPI(wewilldefinitelyaddsecuritylater).ExecuteanyofthefollowingtwocommandsbasedonyourneedstoaccesstheAPIinotherdevicesorcomputersconnectedtoyourLAN.RememberthatweanalyzedthedifferencebetweentheminChapter1,DevelopingRESTfulAPIswithDjango:

pythonmanage.pyrunserver

pythonmanage.pyrunserver0.0.0.0:8000

Afterwerunanyofthepreviouscommands,thedevelopmentserverwillstartlisteningatport8000.

Openawebbrowserandenterhttp://localhost:8000/ortheappropriateURLincaseyouareusinganothercomputerordevicetoaccessthebrowsableAPI.ThebrowsableAPIwillcomposeandsendaGETrequestto/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONresponsefromtheexecutionofthegetmethoddefinedintheApiRootclasswithintheviews.pyfile.ThefollowingscreenshotshowstherenderedwebpageafterenteringtheURLinawebbrowserwiththeresourcedescription:ApiRoot.

TheAPIRootprovidesushyperlinkstoseethelistofgamecategories,games,players,andscores.Thisway,itbecomesextremelyeasytoaccessthelistsandperformoperationsonthedifferentresourcesthroughthebrowsableAPI.Inaddition,whenwevisittheotherURLs,thebreadcrumbwillallowustogobacktotheApiRoot.

InthisnewversionoftheAPI,weworkedwiththegenericviewsthatprovidemanyfeaturedunderthehoods,andtherefore,thebrowsableAPIwillprovideusadditionalfeaturescomparedwiththepreviousversion.ClickortapontheURLontheright-handsideofgame-categories.Incaseyouarebrowsinginlocalhost,theURLwillbe

Page 121: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

http://localhost:8000/game-categories/.ThebrowsableAPIwillrenderthewebpagefortheGameCategoryList.

Atthebottomoftherenderedwebpage,thebrowsableAPIprovidesussomecontrolstogenerateaPOSTrequestto/game-categories/.Inthiscase,bydefault,thebrowsableAPIdisplaystheHTMLformtabwithanautomaticallygeneratedformthatwecanusetogenerateaPOSTrequestwithouthavingtodealwiththerawdataaswedidinourpreviousversion.TheHTMLformsmakeiteasytogeneraterequeststotestourAPI.ThefollowingscreenshotshowstheHTMLformtocreateanewgamecategory:

Wejustneedtoenterthedesiredname,3DRPG,intheNametextboxandclickortaponPOST tocreateanewgamecategory.ThebrowsableAPIwillcomposeandsendaPOSTrequestto/game-categories/withthepreviouslyspecifieddataandwewillseetheresultsofthecallinthewebbrowser.Thefollowingscreenshotshowsawebbrowserdisplayingthe

Page 122: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTPstatuscode201CreatedintheresponseandthepreviouslyexplainedHTMLformwiththePOST buttontoallowustocontinuecomposingandsendingPOSTrequeststo/game-categories/:

Now,clickontheURLdisplayedasavaluefortheurlkeyintheJSONdatadisplayedforthegamecategory,suchashttp://localhost:8000/game-categories/3/.Makesureyoureplace2withtheidorprimarykeyofanexistinggamecategoryinthepreviouslyrenderedGamesList.ThebrowsableAPIwillcomposeandsendaGETrequestto/game-categories/3/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONdataforthegamecategory.ThewebpagewilldisplayaDELETEbuttonbecauseweareworkingwiththeGameCategoryDetailview.

Tip

WecanusethebreadcrumbtogobacktotheApiRootandstartcreatinggamesrelatedtoagamecategory,players,andfinallyscoresrelatedtoagameandaplayer.Wecandoallthis

Page 123: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

witheasytouseHTMLformsandthebrowsableAPIfeature.

Page 124: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingandretrievingrelatedresourcesNow,wewillusetheHTTPiecommandoritscurlequivalentstocomposeandsendHTTPrequeststotheAPI.WewilluseJSONfortherequeststhatrequireadditionaldata.RememberthatyoucanperformthesametaskswithyourfavoriteGUI-basedtoolorwiththebrowsableAPI.

First,wewillcomposeandsendanHTTPrequesttocreateanewgamecategory.RememberthatweusedthebrowsableAPItocreateagamecategorynamed'3DRPG'.

httpPOST:8000/game-categories/name='2Dmobilearcade'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"2Dmobile

arcade"}':8000/game-categories/

TheprecedingcommandwillcomposeandsendaPOSTHTTPrequestwiththespecifiedJSONkey-valuepair.Therequestspecifies/game-categories/,andtherefore,itwillmatch'^game-categories/$'andrunthepostmethodfortheviews.GameCategoryListclass-basedview.RememberthatthemethodisdefinedintheListCreateAPIViewsuperclassanditendsupcallingthecreatemethoddefinedinmixins.CreateModelMixin.IfthenewGameCategoryinstancewassuccessfullypersistedinthedatabase,thecalltothemethodwillreturnanHTTP201CreatedstatuscodeandtherecentlypersistedGameCategoryserializedtoJSONintheresponsebody.ThefollowinglineshowsasampleresponsefortheHTTPrequestwiththenewGameCategoryobjectintheJSONresponse.Theresponsedoesn'tincludetheheader.Notethattheresponseincludesboththeprimarykey,pk,andtheurl,url,forthecreatedcategory.Thegamesarrayisemptybecausetherearen'tgamesrelatedtothenewcategoryyet:

{

"games":[],

"name":"2Dmobilearcade",

"pk":4,

"url":"http://localhost:8000/game-categories/4/"

}

Now,wewillcomposeandsendHTTPrequeststocreatetwogamesthatbelongtothefirstcategorywerecentlycreated:3DRPG.Wewillspecifythegame_categoryvaluewiththenameofthedesiredgamecategory.However,thedatabasetablethatpersiststheGamemodelwillsavethevalueoftheprimarykeyoftherelatedGameCategorywhosenamevaluematchestheoneweprovide:

httpPOST:8000/games/name='PvZGardenWarfare4'game_category='3DRPG'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='SupermanvsAquaman'game_category='3DRPG'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

Thefollowingaretheequivalentcurlcommands:

Page 125: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"PvZGarden

Warfare4","game_category":"3DRPG","played":"false","release_date":

"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Supermanvs

Aquaman","game_category":"3DRPG","played":"false","release_date":"2016-

06-21T03:02:00.776594Z"}':8000/games/

ThepreviouscommandswillcomposeandsendtwoPOSTHTTPrequestswiththespecifiedJSONkey-valuepairs.Therequestspecifies/games/,andtherefore,itwillmatch'^games/$'andrunthepostmethodfortheviews.GameListclass-basedview.ThefollowinglinesshowsampleresponsesforthetwoHTTPrequestswiththenewGameobjectsintheJSONresponses.Theresponsesdon'tincludetheheaders.Notethattheresponseincludesonlytheurl,url,forthecreatedgamesanddoesn'tincludetheprimarykey.Thevalueforgame_categoryisthenamefortherelatedGameCategory:

{

"game_category":"3DRPG",

"name":"PvZGardenWarfare4",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/2/"

}

{

"game_category":"3DRPG",

"name":"SupermanvsAquaman",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/3/"

}

WecanrunthepreviouslyexplainedcommandstocheckthecontentsofthetablesthatDjangocreatedinthePostgreSQLdatabase.Wewillnoticethatthegame_category_idcolumnforthegames_gametablesavesthevalueoftheprimarykeyoftherelatedrowinthegames_game_categorytable.TheGameSerializerclassusestheSlugRelatedFieldtodisplaythenamevaluefortherelatedGameCategory.Thefollowingscreenshotshowsthecontentsofthegames_game_categoryandthegames_gametableinaPostgreSQLdatabaseafterrunningtheHTTPrequests:

Page 126: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,wewillcomposeandsendanHTTPrequesttoretrievethegamecategorythatiscontainstwogames,thatisthegamecategoryresourcewhoseidorprimarykeyisequalto3.Don'tforgettoreplace3withtheprimarykeyvalueofthegamewhosenameisequalto'3DRPG'inyourconfiguration:

http:8000/game-categories/3/

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/game-categories/3/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/game-categories/3/.Therequesthasanumberafter/game-categories/,andtherefore,itwillmatch'^game-categories/(?P<pk>[0-9]+)/$'andrunthegetmethodfortheviews.GameCategoryDetailclassbasedview.RememberthatthemethodisdefinedintheRetrieveUpdateDestroyAPIViewsuperclassanditendsupcallingtheretrieve

Page 127: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

methoddefinedinmixins.RetrieveModelMixin.ThefollowinglinesshowasampleresponsefortheHTTPrequest,withtheGameCategoryobjectandthehyperlinksoftherelatedgamesintheJSONresponse:

HTTP/1.0200OK

Allow:GET,PUT,PATCH,DELETE,HEAD,OPTIONS

Content-Type:application/json

Date:Tue,21Jun201623:32:04GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"games":[

"http://localhost:8000/games/2/",

"http://localhost:8000/games/3/"

],

"name":"3DRPG",

"pk":3,

"url":"http://localhost:8000/game-categories/3/"

}

TheGameCategorySerializerclassdefinedthegamesattributeasaHyperlinkedRelatedField,andtherefore,theserializerrenderstheURLforeachrelatedGameinstanceinthevalueforthegamesarray.IfweviewtheresultsinawebbrowserthroughthebrowsableAPI,wewillbeabletoclickortaponthehyperlinktoseethedetailsforeachgame.

Now,wewillcomposeandsendaPOSTHTTPrequesttocreateagamerelatedtoagamecategorynamethatdoesn'texist:'Virtualreality':

httpPOST:8000/games/name='CaptainAmericavsThor'game_category='Virtual

reality'played=falserelease_date='2016-06-21T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"'Captain

AmericavsThor","game_category":"Virtualreality","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

Djangowon'tbeabletoretrieveaGameCategoryinstancewhosenameisequaltothespecifiedvalue,andtherefore,wewillreceivea400BadRequeststatuscodeintheresponseheaderandamessagerelatedtothevaluespecifiedinforgame_categoryintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BadRequest

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Tue,21Jun201623:51:19GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

Page 128: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"game_category":[

"Objectwithname=Virtualrealitydoesnotexist."

]

}

Now,wewillcomposeandsendHTTPrequeststocreatetwoplayers:

httpPOST:8000/players/name='Brandon'gender='M'

httpPOST:8000/players/name='Kevin'gender='M'

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Brandon",

"gender":"M"}':8000/players/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Kevin",

"gender":"M"}':8000/players/

ThepreviouscommandswillcomposeandsendtwoPOSTHTTPrequestswiththespecifiedJSONkey-valuepairs.Therequestspecifies/players/,andtherefore,itwillmatch'^players/$'andrunthepostmethodfortheviews.PlayerListclassbasedview.ThefollowinglinesshowsampleresponsesforthetwoHTTPrequestswiththenewPlayerobjectsintheJSONresponses.Theresponsesdon'tincludetheheaders.Noticethattheresponseincludesonlytheurl,url,forthecreatedplayersanddoesn'tincludetheprimarykey.Thevalueforgender_descriptionisthechoicedescriptionforthegenderchar.Thescoresarrayisemptybecausetherearen'tscoresrelatedtoeachnewplayeryet:

{

"gender":"M",

"name":"Brandon",

"scores":[],

"url":"http://localhost:8000/players/2/"

}

{

"gender":"M",

"name":"Kevin",

"scores":[],

"url":"http://localhost:8000/players/3/"

}

Now,wewillcomposeandsendHTTPrequeststocreatefourscores:

httpPOST:8000/player-scores/score=35000score_date='2016-06-

21T03:02:00.776594Z'player='Brandon'game='PvZGardenWarfare4'

httpPOST:8000/player-scores/score=85125score_date='2016-06-

22T01:02:00.776594Z'player='Brandon'game='PvZGardenWarfare4'

httpPOST:8000/player-scores/score=123200score_date='2016-06-

22T03:02:00.776594Z'player='Kevin'game='SupermanvsAquaman'

httpPOST:8000/player-scores/score=11200score_date='2016-06-

22T05:02:00.776594Z'player='Kevin'game='PvZGardenWarfare4'

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"score":"35000",

Page 129: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"score_date":"2016-06-21T03:02:00.776594Z","player":"Brandon","game":"PvZ

GardenWarfare4"}':8000/player-scores/

curl-iXPOST-H"Content-Type:application/json"-d'{"score":"85125",

"score_date":"2016-06-22T01:02:00.776594Z","player":"Brandon","game":"PvZ

GardenWarfare4"}':8000/player-scores/

curl-iXPOST-H"Content-Type:application/json"-d'{"score":"123200",

"score_date":"2016-06-22T03:02:00.776594Z","player":"Kevin",

"game":"'SupermanvsAquaman"}':8000/player-scores/

curl-iXPOST-H"Content-Type:application/json"-d'{"score":"11200",

"score_date":"2016-06-22T05:02:00.776594Z","player":"Kevin","game":"PvZ

GardenWarfare4"}':8000/player-scores/

ThepreviouscommandswillcomposeandsendfourPOSTHTTPrequestswiththespecifiedJSONkey-valuepairs.Therequestspecifies/player-scores/,andtherefore,itwillmatch'^player-scores/$'andrunthepostmethodfortheviews.PlayerScoreListclassbasedview.ThefollowinglinesshowsampleresponsesforthefourHTTPrequestswiththenewPlayerobjectsintheJSONresponses.Theresponsesdon'tincludetheheaders.

DjangoRESTFrameworkusesthePlayerScoreSerializerclasstogeneratetheJSONresponse.Thus,thevalueforgameisthenamefortherelatedGameinstanceandthevalueforplayeristhenamefortherelatedPlayerinstance.ThePlayerScoreSerializerclassusedSlugRelatedFieldforbothfields:

{

"game":"PvZGardenWarfare4",

"pk":3,

"player":"Brandon",

"score":35000,

"score_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/player-scores/3/"

}

{

"game":"PvZGardenWarfare4",

"pk":4,

"player":"Brandon",

"score":85125,

"score_date":"2016-06-22T01:02:00.776594Z",

"url":"http://localhost:8000/player-scores/4/"

}

{

"game":"SupermanvsAquaman",

"pk":5,

"player":"Kevin",

"score":123200,

"score_date":"2016-06-22T03:02:00.776594Z",

"url":"http://localhost:8000/player-scores/5/"

}

{

"game":"PvZGardenWarfare4",

"pk":6,

"player":"Kevin",

"score":11200,

"score_date":"2016-06-22T05:02:00.776594Z",

"url":"http://localhost:8000/player-scores/6/"

Page 130: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

}

WecanrunthepreviouslyexplainedcommandstocheckthecontentsofthetablesthatDjangocreatedinthePostgreSQLdatabase.Wewillnoticethatthegame_idcolumnforthegames_playerscoretablesavesthevalueoftheprimarykeyoftherelatedrowinthegames_gametable.Inaddition,theplayer_idcolumnforthegames_playerscoretablesavesthevalueoftheprimarykeyoftherelatedrowinthegames_playertable.Thefollowingscreenshotshowsthecontentsforthegames_game_category,games_game,games_playerandgames_playerscoretablesinaPostgreSQLdatabaseafterrunningtheHTTPrequests:

Now,wewillcomposeandsendanHTTPrequesttoretrieveaspecificplayerthatcontainstwoscores,whichistheplayerresourcewhoseidorprimarykeyisequalto3.Don'tforgettoreplace3withtheprimarykeyvalueoftheplayerwhosenameisequalto'Kevin'inyourconfiguration:

http:8000/players/3/

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/players/3/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8000/players/3/.Therequesthasanumberafter/players/,andtherefore,itwillmatch'^players/(?P<pk>[0-9]+)/$'andrunthegetmethodfortheviews.PlayerDetailclassbasedview.RememberthatthemethodisdefinedintheRetrieveUpdateDestroyAPIViewsuperclassanditendsupcallingtheretrievemethoddefinedinmixins.RetrieveModelMixin.Thefollowinglinesshowasampleresponseforthe

Page 131: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTPrequest,withthePlayerobject,therelatedPlayerScoreobjectsandtheGameobjectrelatedtoeachPlayerScoreobjectintheJSONresponse:

HTTP200OK

Allow:GET,PUT,PATCH,DELETE,HEAD,OPTIONS

Content-Type:application/json

Vary:Accept

{

"url":"http://localhost:8000/players/3/",

"name":"Kevin",

"gender":"M",

"gender_description":"Male",

"scores":[

{

"url":"http://localhost:8000/player-scores/5/",

"pk":5,

"score":123200,

"score_date":"2016-06-22T03:02:00.776594Z",

"game":{

"url":"http://localhost:8000/games/3/",

"game_category":"3DRPG",

"name":"SupermanvsAquaman",

"release_date":"2016-06-21T03:02:00.776594Z",

"played":false

}

},

{

"url":"http://localhost:8000/player-scores/6/",

"pk":6,

"score":11200,

"score_date":"2016-06-22T05:02:00.776594Z",

"game":{

"url":"http://localhost:8000/games/2/",

"game_category":"3DRPG",

"name":"PvZGardenWarfare4",

"release_date":"2016-06-21T03:02:00.776594Z",

"played":false

}

}

]

}

ThePlayerSerializerclassdefinedthescoresattributeasaScoreSerializerwithmanyequaltoTrue,andtherefore,thisserializerrenderseachscorerelatedtotheplayer.TheScoreSerializerclassdefinedthegameattributeasaGameSerializer,andtherefore,thisserializerrenderseachgamerelatedtothescore.IfweviewtheresultsinawebbrowserthroughthebrowsableAPI,wewillbeabletoclickortaponthehyperlinkofeachoftherelatedresources.However,inthiscase,wealsoseealltheirdetailswithouthavingtofollowthehyperlink.

Page 132: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Underthehoods,the@api_viewdecoratoris:

1. Awrapperthatconvertsafunction-basedviewintoasubclassoftherest_framework.views.APIViewclass.

2. Awrapperthatconvertsafunction-basedviewintoaserializer.3. Awrapperthatconvertsafunction-basedviewintoasubclassofthe

rest_framework.views.api_viewclass.

2. ThebrowsableAPI,afeatureincludedinDjangoRESTFrameworkthat:1. Generateshuman-friendlyJSONoutputforeachresourcewhenevertherequest

specifiesapplication/jsonasthevaluefortheContent-typekeyintherequestheader.

2. Generateshuman-friendlyHTMLoutputforeachresourcewhenevertherequestspecifiestext/htmlasthevaluefortheContent-typekeyintherequestheader.

3. Generateshuman-friendlyHTMLoutputforeachresourcewhenevertherequestspecifiesapplication/jsonasthevaluefortheContent-typekeyintherequestheader.

3. Therest_framework.serializers.ModelSerializerclass:1. Automaticallypopulatesbothasetofdefaultconstraintsandasetofdefaultparsers.2. populatesbothasetofdefaultfieldsbutdoesn'tautomaticallypopulateasetof

defaultvalidators.

Automaticallypopulatesbothasetofdefaultfieldsbutdoesn'tautomaticallypopulateasetofdefaultvalidators.Automaticallypopulatesbothasetofdefaultfieldsandasetofdefaultvalidators.

4. Therest_framework.serializers.ModelSerializerclass:1. Providesdefaultimplementationsforthegetandpatchmethods.2. Providesdefaultimplementationsforthegetandputmethods.3. Providesdefaultimplementationsforthecreateandupdatemethods.

5. TheSerializerandModelSerializerclassesinDjangoRESTFrameworkaresimilartothefollowingtwoclassesinDjangoWebFramework:1. FormandModelFormclasses.2. ViewandModelViewclasses.3. ControllerandModelControllerclasses.

Page 133: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wetookadvantageofthevariousfeaturesincludedinDjangoRESTFrameworkthatallowedustoeliminateduplicatecodeandbuildourAPIreusinggeneralizedbehaviors.Weusedmodelserializers,wrappers,defaultparsing,andrenderingoptions,classbasedviews,andgenericclasses.

WeusedthebrowsableAPIfeatureandwedesignedaRESTfulAPIthatinteractedwithacomplexPostgreSQLdatabase.Wedeclaredrelationshipswiththemodels,managedserializationanddeserializationwithrelationships,andhyperlinks.Finally,wecreatedandretrievedrelatedresourcesandweunderstoodhowthingsworkunderthehoods.

NowthatwehavebuiltacomplexAPIwithDjangoRESTFramework,wewilluseadditionalabstractionsincludedintheframeworktoimproveourAPI,wewilladdsecurityandauthentication,whichiswhatwearegoingtodiscussinthenextchapter.

Page 134: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter3.ImprovingandAddingAuthenticationtoanAPIWithDjangoInthischapter,wewillimprovetheRESTfulAPIthatwestartedinthepreviouschapterandalsoaddauthenticationrelatedsecuritytoit.Wewill:

AdduniqueconstraintstothemodelsUpdateasinglefieldforaresourcewiththePATCHmethodTakeadvantageofpaginationCustomizepaginationclassesUnderstandauthentication,permissionsandthrottlingAddsecurity-relateddatatothemodelsCreateacustomizedpermissionclassforobject-levelpermissionsPersisttheuserthatmakesarequestConfigurepermissionpoliciesSetadefaultvalueforanewrequiredfieldinmigrationsComposerequestswiththenecessaryauthenticationBrowsetheAPIwithauthenticationcredentials

Page 135: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AddinguniqueconstraintstothemodelsOurAPIhasafewissuesthatweneedtosolve.Rightnow,itispossibletocreatemanygamecategorieswiththesamename.Weshouldn'tbeabletodoso,andtherefore,wewillmakethenecessarychangestotheGameCategorymodeltoaddauniqueconstraintonthenamefield.WewillalsoaddauniqueconstraintonthenamefieldfortheGameandPlayermodels.Thisway,wewilllearnthenecessarystepstomakechangestotheconstraintsformanymodelsandreflectthechangesintheunderlyingdatabasethroughmigrations.

MakesurethatyouquitDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheterminalorCommandPromptwindowinwhichitisrunning.Now,wewillmakechangestointroduceuniqueconstraintstothenamefieldforthemodelsthatweusetorepresentandpersistthegamecategories,games,andplayers.Openthegames/models.py,fileandreplacethecodethatdeclarestheGameCategory,GameandPlayerclasseswiththefollowingcode.Thethreelinesthatchangearehighlightedinthecodelisting.ThecodeforthePlayerScoreclassremainsthesame.Thecodefileforthesampleisincludedintherestful_python_chapter_03_01folder,asshown:

classGameCategory(models.Model):

name=models.CharField(max_length=200,unique=True)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

classGame(models.Model):

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=200,unique=True)

game_category=models.ForeignKey(

GameCategory,

related_name='games',

on_delete=models.CASCADE)

release_date=models.DateTimeField()

played=models.BooleanField(default=False)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

classPlayer(models.Model):

MALE='M'

FEMALE='F'

GENDER_CHOICES=(

(MALE,'Male'),

(FEMALE,'Female'),

Page 136: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

)

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=50,blank=False,default='',

unique=True)

gender=models.CharField(

max_length=2,

choices=GENDER_CHOICES,

default=MALE,

)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

Wejustneededtoaddunique=Trueasoneofthenamedargumentsformodels.CharField.Thisway,weindicatethatthefieldmustbeuniqueandDjangowillcreatethenecessaryuniqueconstraintsforthefieldsintheunderlyingdatabasetables.

Now,runthefollowingPythonscripttogeneratethemigrationsthatwillallowustosynchronizethedatabasewiththeuniqueconstraintsweaddedforthefieldsinthemodels:

pythonmanage.pymakemigrationsgames

Thefollowinglinesshowtheoutputgeneratedafterrunningthepreviouscommand:

Migrationsfor'games':

0002_auto_20160623_2131.py:

-Alterfieldnameongame

-Alterfieldnameongamecategory

-Alterfieldnameonplayer

Theoutputindicatesthatthegamesapi/games/migrations/0002_auto_20160623_2131.pyfileincludesthecodetoalterthefieldnamednameongame,gamecategory,andplayer.Notethatthegeneratedfilenamewillbedifferentinyourconfigurationbecauseitincludesanencodeddateandtime.Thefollowinglinesshowthecodeforthisfile,whichwasautomaticallygeneratedbyDjango.Thecodefileforthesampleisincludedintherestful_python_chapter_03_01folder:

#-*-coding:utf-8-*-

#GeneratedbyDjango1.9.7on2016-06-2321:31

from__future__importunicode_literals

fromdjango.dbimportmigrations,models

classMigration(migrations.Migration):

dependencies=[

('games','0001_initial'),

]

Page 137: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

operations=[

migrations.AlterField(

model_name='game',

name='name',

field=models.CharField(max_length=200,unique=True),

),

migrations.AlterField(

model_name='gamecategory',

name='name',

field=models.CharField(max_length=200,unique=True),

),

migrations.AlterField(

model_name='player',

name='name',

field=models.CharField(default='',max_length=50,unique=True),

),

]

Thecodedefinesasubclassofthedjango.db.migrations.MigrationclassnamedMigrationthatdefinesanoperationslistwithmanymigrations.AlterField.Eachmigrations.AlterFieldwillalterthefieldinthethetableforeachoftherelatedmodels.

Now,runthefollowingPythonscripttoapplyallthegeneratedmigrationsandexecutethechangesinthedatabasetables:

pythonmanage.pymigrate

Thefollowinglinesshowtheoutputgeneratedafterrunningthepreviouscommand.Notethattheorderingforthemigrationsmightbedifferentinyourconfiguration.

Operationstoperform:

Operationstoperform:

Applyallmigrations:admin,auth,contenttypes,games,sessions

Runningmigrations:

Renderingmodelstates...DONE

Applyinggames.0002_auto_20160623_2131...OK

Afterweruntheprecedingcommand,wewillhaveuniqueindexesonthenamefieldforthegames_game,games_gamecategory,andgames_playertablesinthePostgreSQLdatabase.WecanusethePostgreSQLcommandlineoranyotherapplicationthatallowsustoeasilycheckthecontentsofthePostreSQLdatabasetocheckthetablesthatDjangoupdated.IncaseyoudecidetocontinueworkingwithSQLite,usethecommandsortoolsrelatedtothisdatabase.

Now,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequests.ExecuteanyofthefollowingtwocommandsbasedonyourneedstoaccesstheAPIinotherdevicesorcomputersconnectedtoyourLAN.RememberthatweanalyzedthedifferencebetweentheminChapter1,DevelopingRESTfulAPIswithDjango:

pythonmanage.pyrunserver

pythonmanage.pyrunserver0.0.0.0:8000

Page 138: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Afterwerunanyofthepreviouscommands,thedevelopmentserverwillstartlisteningatport8000.

Now,wewillcomposeandsendanHTTPrequesttocreateagamecategorywithanamethatalreadyexists:'3DRPG':

httpPOST:8000/game-categories/name='3DRPG'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"3DRPG"}'

:8000/game-categories/

Djangowon'tbeabletopersistaGameCategoryinstancewhosenameisequaltothespecifiedvaluebecauseitwouldviolatetheuniqueconstraintaddedtothenamefield.Thus,wewillreceivea400BadRequeststatuscodeintheresponseheaderandamessagerelatedtothevaluespecifiedfornameintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BadRequest

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Sun,26Jun201603:37:05GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"name":[

"GameCategorywiththisnamealreadyexists."

]

}

Afterwehavemadethechanges,wewon'tbeabletoaddduplicatevaluesforthenamefieldingamecategories,games,orplayers.Thisway,wecanbesurethatwheneverwespecifythenameofanyoftheseresources,wearegoingtoreferencethesameuniqueresource.

Page 139: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UpdatingasinglefieldforaresourcewiththePATCHmethodAsweexplainedinChapter2,WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjango,ourAPIcanupdateasinglefieldforanexistingresource,andtherefore,weprovideanimplementationforthePATCHmethod.Forexample,wecanusethePATCHmethodtoupdateanexistinggameandsetthevalueforitsplayedfieldtotrue.Wedon'twanttousethePUTmethodbecausethismethodismeanttoreplaceanentiregame.ThePATCHmethodismeanttoapplyadeltatoanexistinggame,andtherefore,itistheappropriatemethodtojustchangethevalueoftheplayedfield.

Now,wewillcomposeandsendanHTTPrequesttoupdateanexistinggame,specifically,toupdatethevalueoftheplayedfieldandsetittotruebecausewejustwanttoupdateasinglefield,wewillusethePATCHmethodinsteadofPUT.Makesureyoureplace2withtheidorprimarykeyofanexistinggameinyourconfiguration:

httpPATCH:8000/games/2/played=true

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d'{"played":"true"}'

:8000/games/2/

TheprecedingcommandwillcomposeandsendaPATCHHTTPrequestwiththespecifiedJSONkey-valuepair.Therequesthasanumberafter/games/,andtherefore,itwillmatch'^games/(?P<pk>[0-9]+)/$'andrunthepatchmethodfortheviews.GameDetailclass-basedview.RememberthatthemethodisdefinedintheRetrieveUpdateDestroyAPIViewsuperclassanditendsupcallingtheupdatemethoddefinedinmixins.UpdateModelMixin.IftheGameinstanceswiththeupdatedvaluefortheplayedfieldarevalidandweresuccessfullypersistedinthedatabase,thecalltothemethodwillreturna200OKstatuscodeandtherecentlyupdatedGameserializedtoJSONintheresponsebody.Thefollowinglinesshowasampleresponse:

HTTP/1.0200OK

Allow:GET,PUT,PATCH,DELETE,HEAD,OPTIONS

Content-Type:application/json

Date:Sun,26Jun201604:09:22GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"game_category":"3DRPG",

"name":"PvZGardenWarfare4",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/2/"

}

Page 140: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TakingadvantageofpaginationOurdatabasehasafewrowsineachofthetablesthatpersistthemodelswehavedefined.However,afterwestartworkingwithourAPIinareal-lifeproductionenvironment,wewillhavethousandsofplayerscores,players,games,andgamecategories,andtherefore,wewillhavetodealwithlargeresultsets.WecantakeadvantageofthepaginationfeaturesavailableinDjangoRESTFrameworktomakeiteasytospecifyhowwewantlargeresultssetstobesplitintoindividualpagesofdata.

First,wewillcomposeandsendHTTPrequeststocreate10gamesthatbelongtooneofthecategorieswehavecreated:2Dmobilearcade.Thisway,wewillhaveatotalof12gamesthatpersistinthedatabase.Wehad2gamesandwewilladd10more:

httpPOST:8000/games/name='TetrisReloaded'game_category='2Dmobile

arcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='PuzzleCraft'game_category='2Dmobilearcade'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='Blek'game_category='2Dmobilearcade'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='ScribblenautsUnlimited'game_category='2D

mobilearcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='CuttheRope:Magic'game_category='2Dmobile

arcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='TinyDiceDungeon'game_category='2Dmobile

arcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='ADarkRoom'game_category='2Dmobilearcade'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='Bastion'game_category='2Dmobilearcade'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='WelcometotheDungeon'game_category='2Dmobile

arcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

httpPOST:8000/games/name='Dust:AnElysianTail'game_category='2Dmobile

arcade'played=falserelease_date='2016-06-21T03:02:00.776594Z'

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Tetris

Reloaded","game_category":"2Dmobilearcade","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"PuzzleCraft",

"game_category":"2Dmobilearcade","played":"false","release_date":"2016-

06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Blek",

"game_category":"2Dmobilearcade","played":"false","release_date":"2016-

06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Scribblenauts

Unlimited","game_category":"2Dmobilearcade","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"CuttheRope:

Magic","game_category":"2Dmobilearcade","played":"false","release_date":

"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"TinyDice

Page 141: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Dungeon","game_category":"2Dmobilearcade","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"ADarkRoom",

"game_category":"2Dmobilearcade","played":"false","release_date":"2016-

06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Bastion",

"game_category":"2Dmobilearcade","played":"false","release_date":"2016-

06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Welcometothe

Dungeon","game_category":"2Dmobilearcade","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Dust:An

ElysianTail","game_category":"2Dmobilearcade","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

TheprecedingcommandswillcomposeandsendtenPOSTHTTPrequestswiththespecifiedJSONkey-valuepairs.Therequestspecifies/games/,andtherefore,itwillmatch'^games/$'andrunthepostmethodfortheviews.GameListclass-basedview.

Now,wehave12gamesinourdatabase.However,wedon'twanttoretrievethe12gameswhenwecomposeandsendaGETHTTPrequestto/games/.WewillconfigureoneofthecustomizablepaginationstylesincludedinDjangoRESTFrameworktoincludeamaximumoffiveresourcesineachindividualpageofdata.

Tip

OurAPIusesthegenericviewsthatworkwiththemixinclassesthatcanhandlepaginatedresponses,andtherefore,theywillautomaticallytakeintoaccountthepaginationsettingsweconfigureinDjangoRESTFramework.

Openthegamesapi/settings.pyfileandaddthefollowinglinesthatdeclareadictionarynamedREST_FRAMEWORKwithkey-valuepairsthatconfiguretheglobalpaginationsettings.Thecodefileforthesampleisincludedintherestful_python_chapter_03_02folder:

REST_FRAMEWORK={

'DEFAULT_PAGINATION_CLASS':

'rest_framework.pagination.LimitOffsetPagination',

'PAGE_SIZE':5

}

ThevaluefortheDEFAULT_PAGINATION_CLASSsettingskeyspecifiesaglobalsettingwiththedefaultpaginationclassthatthegenericviewswillusetoprovidepaginatedresponses.Inthiscase,wewillusetherest_framework.pagination.LimitOffsetPaginationclass,thatprovidesalimit/offset-basedstyle.Thispaginationstyleworkswithlimitthatindicatesthemaximumnumberofitemstoreturnandanoffsetthatspecifiesthestartingpositionofthequery.ThevalueforthePAGE_SIZEsettingskeyspecifiesaglobalsettingwiththedefaultvalueforthelimit,alsoknownaspagesize.WecanspecifyadifferentlimitwhenweperformtheHTTPrequestbyspecifyingthedesiredvalueinthelimitqueryparameter.Wecanconfiguretheclasstohavethemaximumlimitvalueinordertoavoidtheundesiredhugeresultsets.

Page 142: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,wewillcomposeandsendanHTTPrequesttoretrieveallthegames,specificallythefollowingHTTPGETmethodto/games/:

httpGET:8000/games/

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/games/

Thegenericviewswillusethenewsettingsthatweaddedtoenabletheoffset/limitpaginationandtheresultwillprovideusthefirst5gameresources(resultskey),thetotalnumberofgamesforthequery(countkey),andalinktothenext(nextkey)andprevious(previouskey)pages.Inthiscase,theresultsetisthefirstpage,andtherefore,thelinktothepreviouspage(previouskey)isnull.Wewillreceivea200OKstatuscodeintheresponseheaderandthe5gamesintheresultsarray:

HTTP/1.0200OK

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Fri,01Jul201600:57:55GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"count":12,

"next":"http://localhost:8000/games/?limit=5&offset=5",

"previous":null,

"results":[

{

"game_category":"2Dmobilearcade",

"name":"ADarkRoom",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/10/"

},

{

"game_category":"2Dmobilearcade",

"name":"Bastion",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/11/"

},

{

"game_category":"2Dmobilearcade",

"name":"Blek",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/6/"

},

{

"game_category":"2Dmobilearcade",

"name":"CuttheRope:Magic",

"played":false,

Page 143: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/8/"

},

{

"game_category":"2Dmobilearcade",

"name":"Dust:AnElysianTail",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/13/"

}

]

}

IntheprecedingHTTPrequest,wedidn'tspecifyanyvalueforeitherthelimitoroffsetparameters.However,aswespecifiedthedefaultvalueoflimitas5itemsintheglobalsettings,thegenericviewsusethisconfigurationvalueandprovideuswiththefirstpage.IfwecomposeandsendthefollowingHTTPrequesttoretrievethefirstpageofallthegamesbyspecifying1fortheoffsetvalue,theAPIwillprovidethesameresultsshownbefore:

httpGET':8000/games/?offset=0'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?offset=0'

IfwecomposeandsendthefollowingHTTPrequesttoretrievethefirstpageofallthegamesbyspecifying0fortheoffsetvalueand5forthelimit,theAPIwillalsoprovidethesameresultsasshownearlier:

httpGET':8000/games/?limit=5&offset=0'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?limit=5&offset=0'

Now,wewillcomposeandsendanHTTPrequesttoretrievethenextpage,thatis,thesecondpageforthegames,specificallyanHTTPGETmethodto/games/withtheoffsetvaluesetto5.RememberthatthevalueforthenextkeyreturnedintheJSONbodyofthepreviousresultprovidesuswiththeURLtothenextpage:

httpGET':8000/games/?limit=5&offset=5'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?limit=5&offset=5'

Theresultwillprovideusthesecondsetofthe5gameresource(resultskey),thetotalnumberofgamesforthequery(countkey),andalinktothenext(nextkey)andprevious(previouskey)pages.Inthiscase,theresultsetisthesecondpage,andtherefore,thelinktothepreviouspage(previouskey)ishttp://localhost:8000/games/?limit=5.Wewillreceivea200OKstatuscodeintheresponseheaderandthe5gamesintheresultsarray:

Page 144: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTP/1.0200OK

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Fri,01Jul201601:25:10GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"count":12,

"next":"http://localhost:8000/games/?limit=5&offset=10",

"previous":"http://localhost:8000/games/?limit=5",

"results":[

{

"game_category":"2Dmobilearcade",

"name":"PuzzleCraft",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/5/"

},

{

"game_category":"3DRPG",

"name":"PvZGardenWarfare4",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/2/"

},

{

"game_category":"2Dmobilearcade",

"name":"ScribblenautsUnlimited",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/7/"

},

{

"game_category":"3DRPG",

"name":"SupermanvsAquaman",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/3/"

},

{

"game_category":"2Dmobilearcade",

"name":"TetrisReloaded",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/4/"

}

]

}

IntheprecedingHTTPrequest,wespecifiedvaluesforboththelimitandoffsetparameters.However,aswespecifiedthedefaultvalueoflimitin5itemsintheglobalsettings,thefollowingrequestwillproducethesameresultsthanthepreviousrequest:

httpGET':8000/games/?offset=5'

Page 145: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?offset=5'

Finally,wewillcomposeandsendanHTTPrequesttoretrievethelastpage,thatis,thethirdpageforthegames,specificallyanHTTPGETmethodto/games/withtheoffsetvaluesetto10.RememberthatthevalueforthenextkeyreturnedintheJSONbodyofthepreviousresultprovidesuswiththeURLtothenextpage:

httpGET':8000/games/?limit=5&offset=10'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?limit=5&offset=10'

Theresultwillprovideusthelastsetwith2gameresources(resultskey),thetotalnumberofgamesforthequery(countkey),andalinktothenext(nextkey)andprevious(previouskey)pages.Inthiscase,theresultsetisthelastpage,andtherefore,thelinktothenextpage(nextkey)isnull.Wewillreceivea200OKstatuscodeintheresponseheaderandthe2gamesintheresultsarray:

HTTP/1.0200OK

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Fri,01Jul201601:28:13GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"count":12,

"next":null,

"previous":"http://localhost:8000/games/?limit=5&offset=5",

"results":[

{

"game_category":"2Dmobilearcade",

"name":"TinyDiceDungeon",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/9/"

},

{

"game_category":"2Dmobilearcade",

"name":"WelcometotheDungeon",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/12/"

}

]

}

Page 146: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CustomizingpaginationclassesTherest_framework.pagination.LimitOffsetPaginationclassthatweareusingtoprovidepaginatedresponsesdeclaresamax_limitclassattributethatdefaultstoNone.Thisattributeallowsustoindicatethemaximumallowablelimitthatcanbespecifiedusingthelimitqueryparameter.Withthedefaultsetting,thereisnolimitandwewillbeabletoprocessrequeststhatspecifyavaluefor1000000forthelimitqueryparameter.Wedefinitelydon'twantourAPItobeabletogeneratearesponsewithamillionplayerscoresorplayerswithasinglerequest.Unluckily,thereisnosettingthatallowsustochangethevaluethattheclassassignstothemax_limitclassattribute.Thus,wewillcreateourcustomizedversionofthelimit/offsetpaginationstyleprovidedbyDjangoRESTFramework.

CreateanewPythonfilenamedpagination.pywithinthegamesfolderandenterthefollowingcodewhichdeclaresthenewLimitOffsetPaginationWithMaxLimitclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_03folder:

fromrest_framework.paginationimportLimitOffsetPagination

classLimitOffsetPaginationWithMaxLimit(LimitOffsetPagination):

max_limit=10

TheprecedinglinesdeclaretheLimitOffsetPaginationWithMaxLimitclassasasubclassoftherest_framework.pagination.LimitOffsetPaginationclassandoverridesthevaluespecifiedforthemax_limitclassattributewith10.

Openthegamesapi/settings.pyfileandreplacethelinethatspecifiedthevaluefortheDEFAULT_PAGINATION_CLASSkeyinthedictionarynamedREST_FRAMEWORKwiththehighlightedline.ThefollowinglinesshowthenewdeclarationofthedictionarynamedREST_FRAMEWORK.Thecodefileforthesampleisincludedintherestful_python_chapter_03_03folder:

REST_FRAMEWORK={

'DEFAULT_PAGINATION_CLASS':

'games.pagination.LimitOffsetPaginationWithMaxLimit',

'PAGE_SIZE':5

}

Now,thegenericviewswillusetherecentlydeclaredgames.pagination.LimitOffsetPaginationWithMaxLimitclass,thatprovidesalimit/offsetbasedstylewithamaximumlimitvalueequalto10.Ifarequestspecifiesavalueforlimithigherthan10,theclasswillusethemaximumlimitvalue,thatis,10,andwewillneverreturnmorethan10itemsinapaginatedresponse.

Now,wewillcomposeandsendanHTTPrequesttoretrievethefirstpageforthegames,specificallyanHTTPGETmethodto/games/withthelimitvaluesetto10000:

Page 147: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

httpGET':8000/games/?limit=10000'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?limit=10000'

Theresultwillusealimitvalueequalto10insteadoftheindicated10000becauseweareusingourcustomizedpaginationclass.Theresultwillprovideusthefirstsetwith10gameresources(resultskey),thetotalnumberofgamesforthequery(countkey),andalinktothenext(nextkey)andprevious(previouskey)pages.Inthiscase,theresultsetisthefirstpage,andtherefore,thelinktothenextpage(nextkey)ishttp://localhost:8000/games/?limit=10&offset=10.Wewillreceivea200OKstatuscodeintheresponseheaderandthefirst10gamesintheresultsarray.Thefollowinglinesshowtheheaderandthefirstlinesoftheoutput:

HTTP/1.0200OK

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Fri,01Jul201616:34:01GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

X-Frame-Options:SAMEORIGIN

{

"count":12,

"next":"http://localhost:8000/games/?limit=10&offset=10",

"previous":null,

"results":[

{

Tip

Itisagoodpracticetoconfigureamaximumlimittoavoidgeneratinghugeresponses.

Openawebbrowserandenterhttp://localhost:8000/games/.ReplacelocalhostwiththeIPofthecomputerthatisrunningtheDjangodevelopmentserverincaseyouuseanothercomputerordevicetorunthebrowser.ThebrowsableAPIwillcomposeandsendaGETrequestto/games/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONgameslist;sincewehaveconfiguredpagination,therenderedwebpagewillincludethedefaultpaginationtemplateassociatedwiththebasepaginationclassweareusingandwilldisplaytheavailablepagenumbersattheupper-rightcornerofthewebpage.ThefollowingscreenshotshowstherenderedwebpageafterenteringtheURLinawebbrowserwiththeresourcedescription,GameList,andthethreepages.

Page 148: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Understandingauthentication,permissionsandthrottlingOurcurrentversionoftheAPIprocessesalltheincomingrequestswithoutrequiringanykindofauthentication.DjangoRESTFrameworkallowsustoeasilyusedifferentauthenticationschemestoidentifytheuserthatoriginatedtherequestorthetokenthatsignedtherequest.Then,wecanusethesecredentialstoapplythepermissionandthrottlingpoliciesthatwilldeterminewhethertherequestmustbepermittedornot.

Similartootherconfigurations,wecansettheauthenticationschemesgloballyandthenoverridethemifnecessaryinaclass-basedvieworafunctionview.Alistofclassesspecifiestheauthenticationschemes.DjangoRESTframeworkwilluseallthespecifiedclassesinthelisttoauthenticatearequestbeforerunningthecodefortheview.Thefirstclassinthelistthatgeneratesasuccessfulauthentication,incasewespecifymorethanoneclass,willberesponsibleforsettingthevaluesforthefollowingtwoproperties:

request.user:Theusermodelinstance.Wewilluseaninstanceofthedjango.contrib.auth.Userclass,thatis,aDjangoUserinstance,inourexamples.request.auth:Additionalauthenticationinformation,suchasanauthenticationtoken.

Afterasuccessfulauthentication,wecanusetherequest.userpropertyinourclass-basedviewmethodsthatreceivetherequestparametertoretrieveadditionalinformationabouttheuserthatgeneratedtherequest.

DjangoRESTFrameworkprovidesthefollowingthreeauthenticationclassesintherest_framework.authenticationmodule.AllofthemaresubclassesoftheBaseAuthenticationclass:

BasicAuthentication:ProvidesanHTTPBasicauthenticationagainstusernameandpassword.Ifweuseinproduction,wemustmakesurethattheAPIisonlyavailableoverHTTPS.SessionAuthentication:WorkswithDjango'ssessionframeworkforauthentication.TokenAuthentication:Providesasimpletokenbasedauthentication.TherequestmustincludethetokengeneratedforauserintheAuthorizationHTTPheaderwith"Token"asaprefixforthetoken.

First,wewilluseacombinationofBasicAuthenticationandSessionAuthentication.WecouldalsotakeadvantageoftheTokenAuthenticationclasslater.MakesureyouquittheDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+Cintheterminalorcommand-promptwindowinwhichitisrunning.

Openthegamesapi/settings.pyfileandaddthehighlightedlinestothedictionarynamedREST_FRAMEWORKwithakey-valuepairthatconfigurestheglobaldefaultauthenticationclasses.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04

Page 149: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

folder,asshown:

REST_FRAMEWORK={

'DEFAULT_PAGINATION_CLASS':

'games.pagination.LimitOffsetPaginationWithMaxLimit',

'PAGE_SIZE':5,

'DEFAULT_AUTHENTICATION_CLASSES':(

'rest_framework.authentication.BasicAuthentication',

'rest_framework.authentication.SessionAuthentication',

)

}

ThevaluefortheDEFAULT_AUTHENTICATION_CLASSESsettingskeyspecifiesaglobalsettingwithatupleofstringwhosevaluesindicatetheclassesthatwewanttouseforauthentication.

Permissionsusetheauthenticationinformationincludedintherequest.userandrequest.authpropertiestodeterminewhethertherequestshouldbegrantedordeniedaccess.PermissionsallowustocontrolwhichclassesofuserswillbegrantedordeniedaccesstothedifferentfeaturesorpartsofourAPI.

Forexample,wewillusethepermissionsfeaturesinDjangoRESTframeworktoallowtheauthenticateduserstocreategames.Unauthenticateduserswillonlybeallowedread-onlyaccesstogames.Onlytheuserthatcreatedthegamewillbeabletomakechangestothisgame,andtherefore,wewillmakethenecessarychangesinourAPItomakeagamehaveanowneruser.Wewillusepredefinedpermissionclassesandacustomizedpermissionclasstodefinetheexplainedpermissionpolicies.

Throttlingalsodetermineswhethertherequestmustbeauthorized.ThrottlescontroltherateofrequeststhatuserscanmaketoourAPI.Forexample,wewanttolimitunauthenticateduserstoamaximumof5requestsperhour.Wewanttorestrictauthenticateduserstoamaximumof20requeststothegamesrelatedviewsperday.

Page 150: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Addingsecurity-relateddatatothemodelsWewillassociateagamewithacreatororowner.Onlytheauthenticateduserswillbeabletocreatenewgames.Onlythecreatorofagamewillbeabletoupdateitordeleteit.Alltherequeststhataren'tauthenticatedwillonlyhaveread-onlyaccesstogames.

Openthegames/models.pyfileandreplacethecodethatdeclarestheGameclasswiththefollowingcode.Thelinethatchangesishighlightedinthecodelisting.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder.

classGame(models.Model):

owner=models.ForeignKey(

'auth.User',

related_name='games',

on_delete=models.CASCADE)

created=models.DateTimeField(auto_now_add=True)

name=models.CharField(max_length=200,unique=True)

game_category=models.ForeignKey(

GameCategory,

related_name='games',

on_delete=models.CASCADE)

release_date=models.DateTimeField()

played=models.BooleanField(default=False)

classMeta:

ordering=('name',)

def__str__(self):

returnself.name

TheGamemodeldeclaresanewownerfieldthatusesthedjango.db.models.ForeignKeyclasstoprovideamany-to-onerelationshiptotheauth.Usermodel,specifically,tothedjango.contrib.auth.Usermodel.ThisUsermodelrepresentstheuserswithintheDjangoauthenticationsystem.The'games'valuespecifiedfortherelated_nameargumentcreatesabackwardsrelationfromtheUsermodeltotheGamemodel.ThisvalueindicatesthenametobeusedfortherelationfromtherelatedUserobjectbacktoaGameobject.Thisway,wewillbeabletoaccessallthegamesownedbyaspecificuser.Wheneverwedeleteauser,wewantallthegamesownedbythisusertobedeletedtoo,andtherefore,wespecifiedthemodels.CASCADEvaluefortheon_deleteargument.

Now,wewillrunthecreatesuperusersubcommandformanage.pytocreatethesuperuserforDjangothatwewillusetoeasilyauthenticateourrequests.Wewillcreatemoreuserslater:

pythonmanage.pycreatesuperuser

Thecommandwillaskyoufortheusernameyouwanttouseforthesuperuser.EnterthedesiredusernameandpressEnter.Wewillusesuperuserastheusernameforthisexample.Youwillseealinesimilartothefollowingone:

Page 151: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Username(leaveblanktouse'gaston'):

Then,thecommandwillaskyouforthee-mailaddress.Enterane-mailaddressandpressEnter:

Emailaddress:

Finally,thecommandwillaskyouforthepasswordforthenewsuperuser.EnteryourdesiredpasswordandpressEnter.

Password:

Thecommandwillaskyoutoenterthepasswordagain.EnteritandpressEnter.Ifbothenteredpasswordsmatch,thesuperuserwillbecreated:

Password(again):

Superusercreatedsuccessfully.

Now,gotothegamesapi/gamesfolderandopentheserializers.pyfile.Addthefollowingcodeafterthelastlinethatdeclarestheimports,beforethedeclarationoftheGameCategorySerializerclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

fromdjango.contrib.auth.modelsimportUser

classUserGameSerializer(serializers.HyperlinkedModelSerializer):

classMeta:

model=Game

fields=(

'url',

'name')

classUserSerializer(serializers.HyperlinkedModelSerializer):

games=UserGameSerializer(many=True,read_only=True)

classMeta:

model=User

fields=(

'url',

'pk',

'username',

'games')

TheUserGameSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.Weusethisnewserializerclasstoserializethegamesrelatedtoauser.ThisclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,theGameclass.Thefieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesthatwewanttoincludeintheserializationfromtherelatedmodel.WejustwanttoincludetheURLandthegame'sname,andtherefore,thecodespecified'url'and'name'asmembersofthetuple.Wedon'twanttousethe

Page 152: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

GameSerializerserializerclassforthegamesrelatedtoauserbecausewewanttoserializefewerfields,andtherefore,wecreatedtheUserGameSerializerclass.

TheUserSerializerclassisasubclassoftheHyperlinkedModelSerializerclass.ThisclassdeclaresaMetainnerclassthatdeclarestwoattributes-modelandfields.Themodelattributespecifiesthemodelrelatedtotheserializer,thatis,thedjango.contrib.auth.models.Userclass.

TheUserSerializerclassdeclaresagamesattributeasaninstanceofthepreviouslyexplainedUserGameSerializerwithmanyandread_onlyequaltoTruebecauseitisaone-to-manyrelationshipanditisread-only.Weusethegamesnamethatwespecifiedastherelated_namestringvaluewhenweaddedtheownerfieldasamodels.ForeignKeyinstanceintheGamemodel.Thisway,thegamesfieldwillprovideuswithanarrayofURLsandnamesforeachgamethatbelongstotheuser.

Wewillmakemorechangestotheserializers.pyfileinthegamesapi/gamesfolder.WewilladdanownerfieldtotheexistingGameSerializerclass.ThefollowinglinesshowthenewcodefortheGameSerializerclass.Thenewlinesarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

classGameSerializer(serializers.HyperlinkedModelSerializer):

#Wejustwanttodisplaytheownerusername(read-only)

owner=serializers.ReadOnlyField(source='owner.username')

#Wewanttodisplaythegamecagory'snameinsteadoftheid

game_category=

serializers.SlugRelatedField(queryset=GameCategory.objects.all(),

slug_field='name')

classMeta:

model=Game

depth=4

fields=(

'url',

'owner',

'game_category',

'name',

'release_date',

'played')

Now,theGameSerializerclassdeclaresanownerattributeasaninstanceofserializers.ReadOnlyFieldwithsourceequalto'owner.username'.Thisway,wewillserializethevaluefortheusernamefieldoftherelateddjango.contrib.auth.Userholdintheownerfield.WeusetheReadOnlyFieldbecausetheownerisautomaticallypopulatedwhenanauthenticatedusercreatesagame,andtherefore,itwon'tbepossibletochangetheownerafteragamehasbeencreated.Thisway,theownerfieldwillprovideuswiththeusernamethatcreatedthegame.Inaddition,weadded'owner'tothefield'sstringtuple.

Page 153: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Creatingacustomizedpermissionclassforobject-levelpermissionsCreateanewPythonfilenamedpermissions.pywithinthegamesfolderandenterthefollowingcodethat,declaresthenewIsOwnerOrReadOnlyclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

fromrest_frameworkimportpermissions

classIsOwnerOrReadOnly(permissions.BasePermission):

defhas_object_permission(self,request,view,obj):

ifrequest.methodinpermissions.SAFE_METHODS:

returnTrue

else:

returnobj.owner==request.user

Therest_framework.permissions.BasePermissionclassisthebaseclassfromwhichallpermissionclassesshouldinherit.ThepreviouslinesdeclaretheIsOwnerOrReadOnlyclassasasubclassoftheBasePermissionclassandoverridesthehas_object_permissionmethoddefinedinthesuperclassthatreturnsaboolvalueindicatingwhetherthepermissionshouldbegrantedornot.IftheHTTPverbspecifiedintherequest(request.method)isanyofthethreesafemethodsspecifiedinpermission.SAFE_METHODS(GET,HEAD,orOPTIONS),thehas_object_permissionmethodreturnsTrueandgrantspermissiontotherequest.TheseHTTPverbsdonotmakechangestotherelatedresources,andtherefore,theyareincludedinthepermissions.SAFE_METHODStupleofstring.

IftheHTTPverbspecifiedintherequest(request.method)isnotanyofthethreesafemethods,thecodereturnsTrueandgrantspermissiononlywhentheownerattributeofthereceivedobj(obj.owner)matchestheuserthatcreatedtherequest(request.user).Thisway,onlytheowneroftherelatedresourcewillbegrantedpermissiontorequeststhatincludeHTTPverbsthataren'tsafe.

WewillusethenewIsOwnerOrReadOnlypermissionclasstomakesurethatonlythegameownerscanmakechangestoanexistinggame.Wewillcombinethispermissionclasswiththerest_framework.permissions.IsAuthenticatedOrReadOnlypermissionclassthatonlyallowsread-onlyaccesstoresourceswhentherequestisnotauthenticatedasauser.

Page 154: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

PersistingtheuserthatmakesarequestWewanttobeabletolistalltheusersandretrievethedetailsforasingleuser.Wewillcreatesubclassesofthetwofollowinggenericclassviewsdeclaredinrest_framework.generics:

ListAPIView:ImplementsthegetmethodthatretrievesalistingofaquerysetRetrieveAPIView:Implementsthegetmethodtoretrieveamodelinstance

Gotothegamesapi/gamesfolderandopentheviews.pyfile.Addthefollowingcodeafterthelastlinethatdeclarestheimports,beforethedeclarationoftheGameCategoryListclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

fromdjango.contrib.auth.modelsimportUser

fromgames.serializersimportUserSerializer

fromrest_frameworkimportpermissions

fromgames.permissionsimportIsOwnerOrReadOnly

classUserList(generics.ListAPIView):

queryset=User.objects.all()

serializer_class=UserSerializer

name='user-list'

classUserDetail(generics.RetrieveAPIView):

queryset=User.objects.all()

serializer_class=UserSerializer

name='user-detail'

AddthefollowinghighlightedlinestotheApiRootclassdeclaredintheviews.pyfile.Now,wewillbeabletonavigatetotheuser-relatedviewsthroughoutthebrowsableAPI.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder.

classApiRoot(generics.GenericAPIView):

name='api-root'

defget(self,request,*args,**kwargs):

returnResponse({

'players':reverse(PlayerList.name,request=request),

'game-categories':reverse(GameCategoryList.name,request=request),

'games':reverse(GameList.name,request=request),

'scores':reverse(PlayerScoreList.name,request=request),

'users':reverse(UserList.name,request=request),

})

Gotothegamesapi/gamesfolderandopentheurls.pyfile.Addthefollowingelementstotheurlpatternsstringlist.ThenewstringsdefinetheURLpatternsthatspecifytheregularexpressionsthathavetobematchedintherequesttorunaspecificmethodforthepreviouslycreatedclassbased-viewsintheviews.pyfile:UserListandUserDetail.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

url(r'^users/$',

Page 155: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

views.UserList.as_view(),

name=views.UserList.name),

url(r'^users/(?P<pk>[0-9]+)/$',

views.UserDetail.as_view(),

name=views.UserDetail.name),

Wehavetoaddalineintheurls.pyfileinthegamesapifolder,specifically,thegamesapi/urls.pyfile.ThefiledefinestherootURLconfigurationsandwewanttoincludetheURLpatternstoallowthebrowsableAPItodisplaytheloginandlogoutviews.Thefollowinglinesshowthenewcodeforthegamesapi/urls.pyfile.Thenewlineishighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

fromdjango.conf.urlsimporturl,include

urlpatterns=[

url(r'^',include('games.urls')),

url(r'^api-auth/',include('rest_framework.urls'))

]

WehavetomakechangestotheGameListclass-basedview.Wewilloverridetheperform_createmethodtopopulatetheownerbeforeanewGameinstanceispersistedinthedatabase.ThefollowinglinesshowthenewcodefortheGameListclassintheviews.pyfile.Thenewlinesarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

classGameList(generics.ListCreateAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-list'

defperform_create(self,serializer):

#Passanadditionalownerfieldtothecreatemethod

#ToSettheownertotheuserreceivedintherequest

serializer.save(owner=self.request.user)

TheGameListclassinheritstheperform_createmethodfromtherest_framework.mixins.CreateModelMixinclass.Rememberthatthegenerics.ListCreateAPIViewclassinheritsfromCreateModelMixinclassandotherclasses.Thecodefortheoverriddenperform_createmethodpassesanadditionalownerfieldtothecreatemethodbysettingavaluefortheownerargumentforthecalltotheserializer.savemethod.Thecodesetstheownerattributetothevalueofself.request.user,thatis,totheuserassociatedtotherequest.Thisway,wheneveranewgameispersisted,itwillsavetheuserassociatedtotherequestasitsowner.

Page 156: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConfiguringpermissionpoliciesNow,wewillconfigurepermissionpoliciesfortheclass-basedviewsrelatedtogames.Wewilloverridethevalueforthepermission_classesclassattributefortheGameListandGameDetailclasses.

ThefollowinglinesshowthenewcodefortheGameListclassintheviews.pyfile.Thenewlinesarehighlighted.Don'tremovethecodeweaddedfortheperform_createmethodforthisclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

classGameList(generics.ListCreateAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-list'

permission_classes=(

permissions.IsAuthenticatedOrReadOnly,

IsOwnerOrReadOnly,

)

ThefollowinglinesshowthenewcodefortheGameDetailclassintheviews.pyfile.Thenewlinesarehighlighted.Don'tremovethecodeweaddedfortheperform_createmethodforthisclass.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

classGameDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-detail'

permission_classes=(

permissions.IsAuthenticatedOrReadOnly,

IsOwnerOrReadOnly)

Weaddedthesamelinesinthetwoclasses.WehaveincludedtheIsAuthenticatedOrReadOnlyclassandourpreviouslycreatedIsOwnerOrReadOnlypermissionclassinthepermission_classestuple.

Page 157: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingadefaultvalueforanewrequiredfieldinmigrationsWehavepersistedmanygamesinourdatabaseandaddedanewownerfieldforthegamesthatisarequiredfield.Wedon'twanttodeletealltheexistinggames,andtherefore,wewilltakeadvantageofsomefeaturesinDjangothatmakeiteasyforustomakethechangesintheunderlyingdatabasewithoutlosingtheexistingdata.

Now,weneedtoretrievetheidforthesuperuserwehavecreatedtouseitasthedefaultownerfortheexistinggames.Djangowillallowustoeasilyupdatetheexistinggamestosettheowneruserforthem.

Runthefollowingcommandstoretrievetheidfromtheauth_usertablefortherowthatwhoseusernameisequalto'superuser'.Replacesuperuserwiththeusernameyouselectedforthepreviouslycreatedsuperuser.Inaddition,replaceuser_nameinthecommandwiththeusernameyouusedtocreatethePostgreSQLdatabaseandpasswordwithyourchosenpasswordforthisdatabaseuser.ThecommandassumesthatyouarerunningPostgreSQLonthesamecomputerinwhichyouarerunningthecommand.IncaseyouareworkingwithaSQLitedatabase,youcanruntheequivalentcommandinthePostgreSQLcommandlineoraGUI-basedtooltoexecutethesamequery.

psql--username=user_name--dbname=games--command="SELECTidFROMauth_user

WHEREusername='superuser';"

Thefollowinglinesshowtheoutputwiththevalueforid:1

id

----

1

(1row)

Now,runthefollowingPythonscripttogeneratethemigrationsthatwillallowustosynchronizethedatabasewiththenewfieldweaddedtotheGamemodel:

pythonmanage.pymakemigrationsgames

Djangowilldisplaythefollowingquestion:

Youaretryingtoaddanon-nullablefield'owner'togamewithoutadefault;

wecan'tdothat(thedatabaseneedssomethingtopopulateexistingrows).

Pleaseselectafix:

1)Provideaone-offdefaultnow(willbesetonallexistingrows)

2)Quit,andletmeaddadefaultinmodels.py

Selectanoption:

Wewanttoprovidetheone-offdefaultthatwillbesetonallexistingrows,andtherefore,enter1toselectthefirstoptionandpressEnter.

Page 158: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Djangowilldisplaythefollowingtextaskingustoenterthedefaultvalue:

Pleaseenterthedefaultvaluenow,asvalidPython

Thedatetimeanddjango.utils.timezonemodulesareavailable,soyoucando

e.g.timezone.now()

>>>

Enterthevalueforthepreviouslyretrievedid,1inourexample,andpressEnter.Thefollowinglinesshowtheoutputgeneratedafterrunningtheprecedingcommand:

Migrationsfor'games':

0003_game_owner.py:

-Addfieldownertogame

Theoutputindicatesthatthegamesapi/games/migrations/0003_game_owner.pyfileincludesthecodetoaddthefieldnamedownertogame.ThefollowinglinesshowthecodeforthisfilethatwasautomaticallygeneratedbyDjango.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder:

#-*-coding:utf-8-*-

#GeneratedbyDjango1.9.7on2016-07-0121:06

from__future__importunicode_literals

fromdjango.confimportsettings

fromdjango.dbimportmigrations,models

importdjango.db.models.deletion

classMigration(migrations.Migration):

dependencies=[

migrations.swappable_dependency(settings.AUTH_USER_MODEL),

('games','0002_auto_20160623_2131'),

]

operations=[

migrations.AddField(

model_name='game',

name='owner',

field=models.ForeignKey(default=1,

on_delete=django.db.models.deletion.CASCADE,related_name='games',

to=settings.AUTH_USER_MODEL),

preserve_default=False,

),

]

Thecodedeclaresasubclassofthedjango.db.migrations.MigrationclassnamedMigrationthatdefinesanoperationslistwithamigrations.AddFieldthatwilladdthetheownerfieldtothetablerelatedtothegamemodel.

Now,runthefollowingpythonscripttoapplyallthegeneratedmigrationsandexecutethechangesinthedatabasetables:

pythonmanage.pymigrate

Thefollowinglinesshowtheoutputgeneratedafterrunningthepreviouscommand.Notethattheorderingforthemigrationsmightbedifferentinyourconfiguration:

Page 159: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Operationstoperform:

Applyallmigrations:admin,auth,contenttypes,games,sessions

Runningmigrations:

Renderingmodelstates...DONE

Applyinggames.0003_game_owner...OK

Afterwerunthepreviouscommand,wewillhaveanewowner_idfieldaddedtothegames_gametableinthePostgreSQLdatabase.Theexistingrowsinthegames_gametablewillusethedefaultvalueweindicatedDjangotouseforthenewowner_idfield.WecanusethePostgreSQLcommandlineoranyotherapplicationthatallowsustoeasilycheckthecontentsofthePostreSQLdatabasetocheckthegames_gametablethatDjangoupdated.IncaseyoudecidetocontinueworkingwithSQLite,usethecommandsortoolsrelatedtothisdatabase.

Runthefollowingcommandtolaunchtheinteractiveshell.MakesureyouarewithinthegamesapifolderintheTerminalorCommandPrompt:

pythonmanage.pyshell

Youwillnoticethatalinethatsays(InteractiveConsole)isdisplayedaftertheusuallinesthatintroduceyourdefaultPythoninteractiveshell.EnterthefollowingcodeinthePythoninteractivetocreateanotheruserthatisnotasuperuser.Wewillusethisuserandthesuperusertotestourchangesinthepermissionspolicies.Thecodefileforthesampleisincludedintherestful_python_chapter_03_04folder,intheusers_test_01.pyfile.

Youcanreplacekevinwithyourdesiredusername,kevin@eaxmple.comwiththee-mailandkevinpasswordwiththepasswordyouwanttouseforthisuser.However,takeintoaccountthatwewillbeusingthesecredentialsinthefollowingsections.Makesureyoualwaysreplacethecredentialswithyourowncredentials:

fromdjango.contrib.auth.modelsimportUser

user=User.objects.create_user('kevin','[email protected]','kevinpassword')

user.save()

Finally,quittheinteractiveconsolebyenteringthefollowingcommand:

quit()

Now,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequests.ExecuteanyofthefollowingtwocommandsbasedonyourneedstoaccesstheAPIinotherdevicesorcomputersconnectedtoyourLAN.RememberthatweanalyzedthedifferencebetweentheminChapter1,DevelopingRESTfulAPIswithDjango:

pythonmanage.pyrunserver

pythonmanage.pyrunserver0.0.0.0:8000

Afterwerunanyoftheprecedingcommands,thedevelopmentserverwillstartlisteningatport8000.

Page 160: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ComposingrequestswiththenecessaryauthenticationNow,wewillcomposeandsendanHTTPrequesttocreateanewgamewithoutauthenticationcredentials:

httpPOST:8000/games/name='TheLastofUs'game_category='3DRPG'

played=falserelease_date='2016-06-21T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"TheLastof

Us","game_category":"3DRPG","played":"false","release_date":"2016-06-

21T03:02:00.776594Z"}':8000/games/

Wewillreceivea401Unauthorizedstatuscodeintheresponseheaderandadetailmessageindicatingthatwedidn'tprovideauthenticationcredentialsintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0401Unauthorized

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Sun,03Jul201622:23:07GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

WWW-Authenticate:Basicrealm="api"

X-Frame-Options:SAMEORIGIN

{

"detail":"Authenticationcredentialswerenotprovided."

}

Ifwewanttocreateanewgame,thatis,tomakeaPOSTrequestto/games/,weneedtoprovideauthenticationcredentialsusingHTTPauthentication.Now,wewillcomposeandsendanHTTPrequesttocreateanewgamewithauthenticationcredentials,thatis,withthesuperusernameandhispassword.Remembertoreplacesuperuserwiththenameyouusedforthesuperuserandpasswordwiththepasswordyouconfiguredforthisuser:

http-asuperuser:'password'POST:8000/games/name='TheLastofUs'

game_category='3DRPG'played=falserelease_date='2016-06-

21T03:02:00.776594Z'

Thefollowingistheequivalentcurlcommand:

curl--usersuperuser:'password'-iXPOST-H"Content-Type:application/json"

-d'{"name":"TheLastofUs","game_category":"3DRPG","played":"false",

"release_date":"2016-06-21T03:02:00.776594Z"}':8000/games/

IfthenewGamewiththesuperuseruserasitsownerwassuccessfullypersistedinthedatabase,thefunctionreturnsanHTTP201CreatedstatuscodeandtherecentlypersistedGame

Page 161: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

serializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewGameobjectintheJSONresponse:

HTTP/1.0201Created

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Mon,04Jul201602:45:36GMT

Location:http://localhost:8000/games/16/

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept

X-Frame-Options:SAMEORIGIN

{

"game_category":"3DRPG",

"name":"TheLastofUs",

"owner":"superuser",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/16/"

}

Now,wewillcomposeandsendanHTTPrequesttoupdatetheplayedfieldvalueforthepreviouslycreatedgamewithauthenticationcredentials.However,inthiscase,wewillusetheotheruserwecreatedinDjangotoauthenticatetherequest.Remembertoreplacekevinwiththenameyouusedfortheuserandkevinpasswordwiththepasswordyouconfiguredforthisuser.Inaddition,replace16withtheidgeneratedforthepreviouslycreatedgameinyourconfiguration.WewillusethePATCHmethod.

http-akevin:'kevinpassword'PATCH:8000/games/16/played=true

Thefollowingistheequivalentcurlcommand:

curl--userkevin:'kevinpassword'-iXPATCH-H"Content-Type:

application/json"-d'{"played":"true"}':8000/games/16/

Wewillreceivea403ForbiddenstatuscodeintheresponseheaderandadetailmessageindicatingthatwedonothavepermissiontoperformtheactionintheJSONbody.Theownerforthegamewewanttoupdateissuperuserandtheauthenticationcredentialsforthisrequestuseadifferentuser.Thus,theoperationisrejectedbythehas_object_permissionmethodintheIsOwnerOrReadOnlyclass.Thefollowinglinesshowasampleresponse:

HTTP/1.0403Forbidden

Allow:GET,PUT,PATCH,DELETE,HEAD,OPTIONS

Content-Type:application/json

Date:Mon,04Jul201602:59:15GMT

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept

X-Frame-Options:SAMEORIGIN

{

"detail":"Youdonothavepermissiontoperformthisaction."

}

IfwecomposeandsendanHTTPrequestwiththesameauthenticationcredentialsforthe

Page 162: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

sameresourcewiththeGETmethod,wewillbeabletoretrievethegamethatthespecifieduserdoesn'town.ItwillworkbecauseGETisoneofthesafemethodsandauserthatisnottheownerisallowedtoreadtheresource.Remembertoreplacekevinwiththenameyouusedfortheuserandkevinpasswordwiththepasswordyouconfiguredforthisuser.Inaddition,replace16withtheidgeneratedforthepreviouslycreatedgameinyourconfiguration:

http-akevin:'kevinpassword'GET:8000/games/16/

Thefollowingistheequivalentcurlcommand:

curl--userkevin:'kevinpassword'-iXGET:8000/games/16/

Page 163: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BrowsingtheAPIwithauthenticationcredentialsOpenawebbrowserandenterhttp://localhost:8000/.ReplacelocalhostbytheIPofthecomputerthatisrunningtheDjangodevelopmentserverincaseyouuseanothercomputerordevicetorunthebrowser.ThebrowsableAPIwillcomposeandsendaGETrequestto/andwilldisplaytheresultsofitsexecution,thatis,theApiRoot.YouwillnoticethatthereisaLoginhyperlinkintheupper-rightcorner.

ClickLoginandthebrowserwilldisplaytheDjangoRESTFrameworkloginpage.Enterkevininusername,kevinpasswordinpassword,andclickLogIn.Remembertoreplacekevinwiththenameyouusedfortheuserandkevinpasswordwiththepasswordyouconfiguredforthisuser.Now,youwillbeloggedinaskevinandalltherequestsyoucomposeandsendthroughthebrowsableAPIwillusethisuser.YouwillberedirectedagaintotheApiRootandyouwillnoticetheLogInhyperlinkisreplacedwiththeusername(kevin)andadrop-downmenuthatallowsyoutoLogOut.ThefollowingscreenshotshowstheApiRootafterweareloggedinaskevin.

ClickortapontheURLontheright-handsideofusers.Incaseyouarebrowsinginlocalhost,theURLwillbehttp://localhost:8000/users/.TheBrowsableAPIwillrenderthewebpagefortheUsersList.ThefollowinglinesshowtheJSONbodywiththefirstlinesandthelastlineswiththeresultsfortheGETrequesttolocalhost:8000/users/.

ThegamesarrayincludestheURLandthenameforeachgamethattheuserownsbecausetheUserGameSerializerclassisserializingthecontentforeachgame:

HTTP200OK

Allow:GET,HEAD,OPTIONS

Content-Type:application/json

Vary:Accept

{

"count":2,

"next":null,

"previous":null,

"results":[

{

"url":"http://localhost:8000/users/1/",

"pk":1,

"username":"superuser",

"games":[

{

"url":"http://localhost:8000/games/10/",

"name":"ADarkRoom"

},

{

"url":"http://localhost:8000/games/11/",

"name":"Bastion"

},

Page 164: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

...

]

},

{

"url":"http://localhost:8000/users/3/",

"pk":3,

"username":"kevin",

"games":[]

}

]

}

ClickortapononeoftheURLsforthegameslistedasownedbythesuperuseruser.TheBrowsableAPIwillrenderthewebpagefortheGameDetail.ClickortaponOPTIONSandtheDELETEbuttonwillappear.ClickortaponDELETE.Thewebbrowserwilldisplayaconfirmationdialogbox.ClickortaponDELETE.Wewillreceivea403ForbiddenstatuscodeintheresponseheaderandadetailmessageindicatingthatwedonothavepermissiontoperformtheactionintheJSONbody.

Theownerforthegamewewanttodeleteissuperuserandtheauthenticationcredentialsforthisrequestuseadifferentuser,specifically,kevin.Thus,theoperationisrejectedbythehas_object_permissionmethodintheIsOwnerOrReadOnlyclass.Thefollowingscreenshotshowsasampleresponse:

Page 165: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Tip

WecanalsotakeadvantageofotherauthenticationpluginsthatDjangoRESTFrameworkprovidesus.Youcanreadmoreaboutallthepossibilitiesthattheframeworkprovidesusforauthenticationathttp://www.django-rest-framework.org/api-guide/authentication/

Page 166: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. WhichisthemostappropriateHTTPmethodtoupdateasinglefieldforanexisting

resource:1. PUT2. POST3. PATCH

2. Whichofthefollowingpaginationclassesprovidesalimit/offsetbasedstyleinDjangoRESTFramework:1. rest_framework.pagination.LimitOffsetPagination2. rest_framework.pagination.LimitOffsetPaging3. rest_framework.styles.LimitOffsetPagination

3. Therest_framework.authentication.BasicAuthenticationclass:1. WorkswithDjango'ssessionframeworkforauthentication.2. ProvidesanHTTPBasicauthenticationagainstusernameandpassword.3. Providesasimpletokenbasedauthentication.

4. Therest_framework.authentication.SessionAuthenticationclass:1. WorkswithDjango'ssessionframeworkforauthentication.2. ProvidesanHTTPBasicauthenticationagainstusernameandpassword.3. Providesasimpletokenbasedauthentication.

5. Thevalueofwhichofthefollowingsettingskeysspecifyaglobalsettingwithatupleofstringwhosevaluesindicatetheclassesthatwewanttouseforauthentication:1. DEFAULT_AUTH_CLASSES2. AUTHENTICATION_CLASSES3. DEFAULT_AUTHENTICATION_CLASSES

Page 167: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,weimprovedtheRESTAPIinmanyways.Weaddeduniqueconstraintstothemodelandupdatedthedatabase,wemadeiteasytoupdatesinglefieldswiththePATCHmethodandwetookadvantageofpagination.

Then,westartedworkingwithauthentication,permissions,andthrottling.Weaddedsecurity-relateddatatothemodelsandweupdatedthedatabase.WemadenumerouschangesinthedifferentpiecesofcodetoachieveaspecificsecuritygoalandwetookadvantageofDjangoRESTFrameworkauthenticationandpermissionsfeatures.

NowthatwehavebuiltanimprovedandcomplexAPIthattakesintoaccountauthenticationandusespermissionpolicies,wewilluseadditionalabstractionsincludedintheframework,wewilladdthrottlingandtests,whichiswhatwearegoingtodiscussinthenextchapter.

Page 168: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter4.Throttling,Filtering,Testing,andDeployinganAPIwithDjangoInthischapter,wewillusetheadditionalfeaturesincludedinDjangoandDjangoRESTFrameworktoimproveourRESTfulAPI.Wewillalsowriteandexecuteunittestsandlearnafewthingsrelatedtodeployment.Wewillcoverthefollowingtopicsinthischapter:

UnderstandingthrottlingclassesConfiguringthrottlingpoliciesTestingthrottlepoliciesUnderstandingfiltering,searchingandorderingclassesConfiguringfiltering,searching,andorderingforviewsTestingfiltering,searchingandorderingfeaturesFilter,search,andorderinthebrowsableAPIWritingafirstroundofunittestsRunningunittestsandcheckingtestingcoverageImprovingtestingcoverageUnderstandingstrategiesfordeploymentsandscalability

Page 169: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthrottlingclassesSofar,wehaven'testablishedanylimitsontheusageofourAPI,andtherefore,bothauthenticatedandunauthenticateduserscancomposeandsendasmanyrequestsastheywantto.WeonlytookadvantageofthepaginationfeaturesavailableinDjangoRESTFrameworktospecifyhowwewantedlargeresultssetstobesplitintoindividualpagesofdata.However,anyusercancomposeandsendthousandsofrequeststobeprocessedwithoutanykindoflimitation.

WewillusethrottlingtoconfigurethefollowinglimitationsoftheusageofourAPI:

Unauthenticatedusers:Amaximumoffiverequestsperhour.Authenticatedusers:Amaximumof20requestsperhour.

Inaddition,wewanttoconfigureamaximumof100requestsperhourtothegamecategoriesrelatedviews,nomatterwhethertheuserisauthenticatedornot.

DjangoRESTFrameworkprovidesthefollowingthreethrottlingclassesintherest_framework.throttlingmodule.AllofthemaresubclassesoftheSimpleRateThrottleclass,whichisasubclassoftheBaseThrottleclass.Theclassesallowustosetthemaximumnumberofrequestsperperiodthatarecomputedbasedondifferentmechanismstodeterminethepreviousrequestinformationusedtospecifythescope.Thepreviousrequestinformationforthrottlingisstoredinthecacheandtheclassesoverridetheget_cache_keymethodthatdeterminesthescope.

AnonRateThrottle:Thisclasslimitstherateofrequestthatananonymoususercanmake.TheIPaddressoftherequestistheuniquecachekey,andtherefore,alltherequestscomingfromthesameIPaddresswillaccumulatethetotalnumberofrequests.UserRateThrottle:Thisclasslimitstherateatwhichaspecificusercanmakerequests.Forauthenticatedusers,theauthenticateduserIDistheuniquecachekey.Foranonymoususers,theIPaddressoftherequestistheuniquecachekey.ScopedRateThrottle:ThisclasslimitstherateofrequestforspecificpartsoftheAPIidentifiedwiththevalueassignedtothethrottle_scopeproperty.TheclassisusefulwhenwewanttorestrictaccesstospecificpartsoftheAPIwithdifferentrates.

Page 170: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConfiguringthrottlingpoliciesWewilluseacombinationofthethreethrottlingclasses,discussedearlier,toachieveourpreviouslyexplainedgoals.MakesureyouquitDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheTerminalorCommandPromptwindowinwhichitisrunning.

Openthegamesapi/settings.pyfileandaddthehighlightedlinestothedictionarynamedREST_FRAMEWORKwithtwokey-valuepairsthatconfiguretheglobaldefaultthrottlingclassesandtheirrates.Thecodefileforthesampleisincludedintherestful_python_chapter_04_01folder:

REST_FRAMEWORK={

'DEFAULT_PAGINATION_CLASS':

'games.pagination.LimitOffsetPaginationWithMaxLimit',

'PAGE_SIZE':5,

'DEFAULT_AUTHENTICATION_CLASSES':(

'rest_framework.authentication.BasicAuthentication',

'rest_framework.authentication.SessionAuthentication',

),

'DEFAULT_THROTTLE_CLASSES':(

'rest_framework.throttling.AnonRateThrottle',

'rest_framework.throttling.UserRateThrottle',

),

'DEFAULT_THROTTLE_RATES':{

'anon':'5/hour',

'user':'20/hour',

'game-categories':'30/hour',

}

}

ThevaluefortheDEFAULT_THROTTLE_CLASSESsettingskeyspecifiesaglobalsettingwithatupleofstringwhosevaluesindicatethedefaultclassesthatwewanttouseforthrottling-AnonRateThrottleandUserRateThrottle.TheDEFAULT_THROTTLE_RATESsettingskeyspecifiesadictionarywithdefaultthrottlerates.Thevaluespecifiedforthe'anon'keyindicatesthatwewantamaximumoffiverequestsperhourforanonymoususers.Thevaluespecifiedforthe'user'keyindicatesthatwewantamaximumof20requestsperhourforauthenticatedusers.Thevaluespecifiedforthe'game-categories'keyindicatesthatwewantamaximumof30requestsperhourforthescopewiththatname.

Themaximumrateisastringthatspecifiesthenumberofrequestsperperiodwiththefollowingformat:'number_of_requests/period',whereperiodcanbeanyofthefollowing:

s:secondsec:secondm:minutemin:minuteh:hour

Page 171: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

hour:hourd:dayday:day

Now,wewillconfigurethrottlingpoliciesfortheclass-basedviewsrelatedtogamecategories.Wewilloverridethevalueforthethrottle_scopeandthrottle_classesclassattributesfortheGameCategoryListandGameCategoryDetailclasses.First,wehavetoaddthefollowingimportstatementafterthelastimportintheviews.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_04_01folder:

fromrest_framework.throttlingimportScopedRateThrottle

ThefollowinglinesshowthenewcodefortheGameCategoryListclassintheviews.pyfile.Thenewlinesarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_04_01folder:

classGameCategoryList(generics.ListCreateAPIView):

queryset=GameCategory.objects.all()

serializer_class=GameCategorySerializer

name='gamecategory-list'

throttle_scope='game-categories'

throttle_classes=(ScopedRateThrottle,)

ThefollowinglinesshowthenewcodefortheGameCategoryDetailclassintheviews.pyfile.Thenewlinesarehighlightedinthefollowingcode.Thecodefileforthesampleisincludedintherestful_python_chapter_04_01folder:

classGameCategoryDetail(generics.RetrieveUpdateDestroyAPIView):

queryset=GameCategory.objects.all()

serializer_class=GameCategorySerializer

name='gamecategory-detail'

throttle_scope='game-categories'

throttle_classes=(ScopedRateThrottle,)

Weaddedthesamelinesinthetwoclasses.Weset'game-categories'asthevalueforthethrottle_scopeclassattributeandweincludedScopedRateThrottleinthetuplethatdefinesthevalueforthrottle_classes.Thisway,thetwoclass-basedviewswillusethesettingsspecifiedforthe'game-categories'scopeandtheScopeRateThrottleclassforthrottling.Theseviewswillbeabletoserve30requestsperhourandwon'ttakeintoaccounttheglobalsettingsthatapplytothedefaultclassesthatweuseforthrottling:AnonRateThrottleandUserRateThrottle.

BeforeDjangorunsthemainbodyofaview,itperformsthechecksforeachthrottleclassspecifiedinthethrottleclasses.Intheviewsrelatedtothegamecategories,wewrotecodethatoverridesthedefaultsettings.Ifasinglethrottlecheckfails,thecodewillraiseaThrottledexceptionandDjangowon'texecutethemainbodyoftheview.Thecacheisresponsibleofstoringpreviousrequests'informationforthrottlingchecking.

Page 172: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TestingthrottlingpoliciesNow,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequests.ExecuteanyofthefollowingtwocommandsbasedonyourneedstoaccesstheAPIinotherdevicesorcomputersconnectedtoyourLAN.RememberthatweanalyzedthedifferencebetweentheminChapter1,DevelopingRESTfulAPIswithDjango.

pythonmanage.pyrunserver

pythonmanage.pyrunserver0.0.0.0:8000

Afterwerunanyofthepreviouscommands,thedevelopmentserverwillstartlisteningatport8000.

Now,wewillcomposeandsendanHTTPrequesttoretrievealltheplayer'sscoreswithoutauthenticationcredentialssixtimes:

http:8000/player-scores/

WecanalsousethefeaturesoftheshellinmacOSorLinuxtorunthepreviouscommandsixtimeswithjustasingleline.WecanalsorunthecommandinaCygwinterminalinWindows.Wecanexecutethenextlineinabashshell.However,wewillseealltheresultsoneaftertheotherandyouwillhavetoscrolltounderstandwhathappenedwitheachexecution:

foriin{1..6};dohttp:8000/player-scores/;done;

Thefollowingistheequivalentcurlcommandthatwemustexecutesixtimes:

curl-iXGET:8000/player-scores/

ThefollowingistheequivalentcurlcommandthatisexecutedsixtimeswithasinglelineinabashshellinmacOSorLinux,oraCygwinterminalinWindows:

foriin{1..6};docurl-iXGET:8000/player-scores/;done;

Djangowon'tprocessthesixthrequestbecauseAnonRateThrottleisconfiguredasoneofthedefaultthrottleclassesanditsthrottlesettingsspecifyfiverequestsperhour.Thus,wewillreceivea429Toomanyrequestsstatuscodeintheresponseheaderandamessageindicatingthattherequestwasthrottledandthetimeinwhichtheserverwillbeabletoprocessanadditionalrequest.TheRetry-Afterkeyintheresponseheaderprovidesthenumberofsecondsthatitisnecessarytowaituntilthenextrequest:3189.Thefollowinglinesshowasampleresponse:

HTTP/1.0429TooManyRequests

Allow:GET,POST,HEAD,OPTIONS

Content-Type:application/json

Date:Tue,05Jul201603:37:50GMT

Retry-After:3189

Server:WSGIServer/0.2CPython/3.5.1

Vary:Accept,Cookie

Page 173: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

X-Frame-Options:SAMEORIGIN

{

"detail":"Requestwasthrottled.Expectedavailablein3189seconds."

}

Now,wewillcomposeandsendanHTTPrequesttoretrievetheplayer'sscoreswithauthenticationcredentials,thatis,withthesuperusernameandhispassword.Wewillexecutethesamerequestsixtimes.RemembertoreplacesuperuserwiththenameyouusedforthesuperuserandpasswordwiththepasswordyouconfiguredforthisuserinChapter3,ImprovingandAddingAuthenticationtoanAPIwithDjango:

http-asuperuser:'password':8000/player-scores/

Wecanalsorunthepreviouscommandsixtimeswithjustasingleline:

foriin{1..6};dohttp-asuperuser:'password':8000/player-scores/;done;

Thefollowingistheequivalentcurlcommandthatwemustexecutesixtimes:

curl--usersuperuser:'password'-iXGET:8000/player-scores/

Thefollowingistheequivalentcurlcommandthatisexecutedsixtimeswithasingleline:

foriin{1..6};docurl--usersuperuser:'password'-iXGET:8000/player-

scores/;done;

Djangowillprocessthesixthrequestbecausewehavecomposedandsentsixauthenticatedrequestswiththesameuser,UserRateThrottleisconfiguredasoneofthedefaultthrottleclassesanditsthrottlesettingsspecify20requestsperhour.

Ifwerunthepreviouscommands15timesmore,wewillaccumulate21requestsandwewillwillreceivea429Toomanyrequestsstatuscodeintheresponseheaderandamessageindicatingthattherequestwasthrottledandthetimeinwhichtheserverwillbeabletoprocessanadditionalrequestafterthelastexecution.

Now,wewillcomposeandsendanHTTPrequesttoretrieveallthegamecategoriesthirtytimeswithouttheauthenticationcredentials:

http:8000/game-categories/

Wecanalsorunthepreviouscommandthirtytimeswithjustasingleline:

foriin{1..30};dohttp:8000/game-categories/;done;

Thefollowingistheequivalentcurlcommandthatwemustexecutethirtytimes:

curl-iXGET:8000/game-categories/

Thefollowingistheequivalentcurlcommandthatisexecutedthirtytimeswithasingleline:

foriin{1..30};docurl-iXGET:8000/game-categories/;done;

Page 174: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Djangowillprocessthethirtyrequestsbecausewehavecomposedandsent30unauthenticatedrequeststoaURLthatisidentifiedwiththe'game-categories'throttlescopeandusestheScopedRateThrottleclassforthrottlepermissioncontrol.Thethrottlesettingsforthethrottlescopeidentifiedwith'game-categories'areconfiguredwith30requestsperhour.

Ifwerunthepreviouscommandonceagain,wewillaccumulate31requestsandwewillreceivea429Toomanyrequestsstatuscodeintheresponseheaderandamessageindicatingthattherequestwasthrottledandthetimeinwhichtheserverwillbeabletoprocessanadditionalrequestafterthelastexecution.

Page 175: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Understandingfiltering,searching,andorderingclassesWetookadvantageofthepaginationfeaturesavailableinDjangoRESTFrameworktospecifyhowwewantedlargeresultssetstobesplitintoindividualpagesofdata.However,wehavealwaysbeenworkingwiththeentirequerysetastheresultset.DjangoRESTFrameworkmakesiteasytocustomizefiltering,searching,andsortingcapabilitiestotheviewswehavealreadycoded.

First,wewillinstallthedjango-filterpackageinourvirtualenvironment.Thisway,wewillbeabletousefieldfilteringfeaturesthatwecaneasilycustomizeinDjangoRESTFramework.MakesurethatyouquittheDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheterminalorCommandPromptwindowinwhichitisrunning.Then,wejustneedtorunthefollowingcommandtoinstallthedjango-filterpackage:

pipinstalldjango-filter

Thelastlinesfortheoutputwillindicatethatthedjango-filterpackagehasbeensuccessfullyinstalled.

Collectingdjango-filter

Downloadingdjango_filter-0.13.0-py2.py3-none-any.whl

Installingcollectedpackages:django-filter

Successfullyinstalleddjango-filter-0.13.0

Inaddition,wewillinstallthedjango-cripsy-formspackageinourvirtualenvironment.ThispackageenhanceshowthebrowsableAPIrendersthedifferentfilters.Runthefollowingcommandtoinstallthedjango-cripsy-formspackage:Wejustneedtorunthefollowingcommandtoinstallthispackage:

pipinstalldjango-crispy-forms

Thelastlinesfortheoutputwillindicatethatthedjango-crispy-formspackagehasbeensuccessfullyinstalled:

Collectingdjango-crispy-forms

Installingcollectedpackages:django-crispy-forms

Runningsetup.pyinstallfordjango-crispy-forms

Successfullyinstalleddjango-crispy-forms-1.6.0

Openthegamesapi/settings.pyfileandaddthehighlightedlinestotheREST_FRAMEWORKdictionary.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

REST_FRAMEWORK={

'DEFAULT_PAGINATION_CLASS':

'games.pagination.LimitOffsetPaginationWithMaxLimit',

'PAGE_SIZE':5,

Page 176: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

'DEFAULT_FILTER_BACKENDS':(

'rest_framework.filters.DjangoFilterBackend',

'rest_framework.filters.SearchFilter',

'rest_framework.filters.OrderingFilter',

),

'DEFAULT_AUTHENTICATION_CLASSES':(

'rest_framework.authentication.BasicAuthentication',

'rest_framework.authentication.SessionAuthentication',

),

'DEFAULT_THROTTLE_CLASSES':(

'rest_framework.throttling.AnonRateThrottle',

'rest_framework.throttling.UserRateThrottle',

),

'DEFAULT_THROTTLE_RATES':{

'anon':'5/hour',

'user':'20/hour',

'game-categories':'30/hour',

}

}

Thevalueforthe'DEFAULT_FILTER_BACKENDSsettingskeyspecifiesaglobalsettingwithatupleofstringwhosevaluesindicatethedefaultclassesthatwewanttouseforfilterbackends.Wewillusethefollowingthreeclasses:

rest_framework.filters.DjangoFilterBackend:Thisclassprovidesfieldfilteringcapabilities.Itusesthepreviouslyinstalleddjango-filterpackage.Wecanspecifythesetoffieldswewanttobeabletofilteragainstorcreatearest_framework.filters.FilterSetclasswithmorecustomizedsettingsandassociateitwiththeview.rest_framework.filters.SearchFilter:Thisclassprovidessinglequeryparameter-basedsearchingcapabilitiesanditisbasedonDjangoadmin'ssearchfunction.Wecanspecifythesetoffieldswewanttoincludeforthesearchandtheclientwillbeabletofilteritemsbymakingqueriesthatsearchthesefieldswithasinglequery.Itisusefulwhenwewanttomakeitpossibleforarequesttosearchmultiplefieldswithasinglequery.rest_framework.filters.OrderingFilter:Thisclassallowstheclienttocontrolhowtheresultsareorderedwithasingle-queryparameter.Wecanalsospecifythefieldsthatcanbeorderedagainst.

Tip

Wecanalsoconfigurethefilterbackendsbyincludinganyofthepreviouslyenumeratedclassesinatupleandassignittothefilter_backendsclassattributeforthegenericviewclasses.However,inthiscase,wewillusethedefaultconfigurationforallourclass-basedviews.

Add'crispy_forms'totheinstalledappsinthesettings.pyfile,specifically,totheINSTALLED_APPSstringlist.Thefollowingcodeshowsthelineswemustaddasthehighlightedcode.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

Page 177: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

INSTALLED_APPS=[

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

#DjangoRESTFramework

'rest_framework',

#Gamesapplication

'games.apps.GamesConfig',

#Crispyforms

'crispy_forms',

]

Tip

Wehavetobecarefulwiththefieldsweconfiguretobeavailableinthefiltering,searching,andorderingfeatures.Theconfigurationwillhaveanimpactonthequeriesexecutedonthedatabase,andtherefore,wemustensurethatwehavetheappropriatedatabaseoptimizationsconsideringthequeriesthatwillbeexecuted.

Page 178: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Configuringfiltering,searching,andorderingforviewsGotothegamesapi/gamesfolderandopentheviews.pyfile.AddthefollowingcodeafterthelastlinethatdeclarestheimportsbutbeforethedeclarationoftheUserListclass.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

fromrest_frameworkimportfilters

fromdjango_filtersimportNumberFilter,DateTimeFilter,AllValuesFilter

AddthefollowinghighlightedlinestotheGameCategoryListclassdeclaredintheviews.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

classGameCategoryList(generics.ListCreateAPIView):

queryset=GameCategory.objects.all()

serializer_class=GameCategorySerializer

name='gamecategory-list'

throttle_scope='game-categories'

throttle_classes=(ScopedRateThrottle,)

filter_fields=('name',)

search_fields=('^name',)

ordering_fields=('name',)

Thefilter_fieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesthatwewanttobeabletofilteragainst.Underthehoods,DjangoRESTFrameworkwillautomaticallycreatearest_framework.filters.FilterSetclassandassociateittotheGameCategoryListview.Thisway,wewillbeabletofilteragainstthenamefield.

Thesearch_fieldsattributespecifiesatupleofstringwhosevaluesindicatethetext-typefieldnamesthatwewanttoincludeinthesearchfeature.Inthiscase,wewanttosearchonlyagainstthenamefieldandperformastarts-withmatch.The'^'includedasaprefixofthefieldnameindicatesthatwewanttorestrictthesearchbehaviortoastarts-withmatch.

Theordering_fieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesthattheclientcanspecifytosorttheresults.Incasetheclientdoesn'tspecifyafieldforordering,theresponsewillusethedefaultorderingfieldsindicatedinthemodelrelatedtotheview.

AddthefollowinghighlightedlinestotheGameListclassdeclaredintheviews.pyfile.Thenewlinesspecifythefieldstobeusedinthefilter,search,andorderingfeatures.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

classGameList(generics.ListCreateAPIView):

queryset=Game.objects.all()

serializer_class=GameSerializer

name='game-list'

permission_classes=(

Page 179: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

permissions.IsAuthenticatedOrReadOnly,

IsOwnerOrReadOnly,

)

filter_fields=(

'name',

'game_category',

'release_date',

'played',

'owner',

)

search_fields=(

'^name',

)

ordering_fields=(

'name',

'release_date',

)

defperform_create(self,serializer):

serializer.save(owner=self.request.user)

Inthiscase,wespecifiedmanyfieldnamesinthefilter_fieldsattribute.Weincluded'game_category'and'owner'inthestringtuple,andtherefore,theclientwillbeabletoincludetheidvaluesforanyofthesetwofieldsinthefilter.Wewilltakeadvantageofotheroptionsforrelatedmodels,whichwilllaterallowustofiltertherelatedmodelsbyfield.Thisway,wewillunderstandthedifferentcustomizationsavailable.

Theordering_fieldsattributespecifiestwofieldnamesforthetupleofstring,andtherefore,theclientwillbeabletoordertheresultsbyeithernameorrelease_date.

AddthefollowinghighlightedlinestothePlayerListclassdeclaredintheviews.pyfile.Thenewlinesspecifythefieldstobeusedinthefilter,search,andorderingfeatures.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

classPlayerList(generics.ListCreateAPIView):

queryset=Player.objects.all()

serializer_class=PlayerSerializer

name='player-list'

filter_fields=(

'name',

'gender',

)

search_fields=(

'^name',

)

ordering_fields=(

'name',

)

AddthefollowinglinestocreatethenewPlayerScoreFilterclassintheviews.pyfilebutbeforethedeclarationofthePlayerScoreListclass.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

Page 180: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

classPlayerScoreFilter(filters.FilterSet):

min_score=NumberFilter(

name='score',lookup_expr='gte')

max_score=NumberFilter(

name='score',lookup_expr='lte')

from_score_date=DateTimeFilter(

name='score_date',lookup_expr='gte')

to_score_date=DateTimeFilter(

name='score_date',lookup_expr='lte')

player_name=AllValuesFilter(

name='player__name')

game_name=AllValuesFilter(

name='game__name')

classMeta:

model=PlayerScore

fields=(

'score',

'from_score_date',

'to_score_date',

'min_score',

'max_score',

#player__namewillbeaccessedasplayer_name

'player_name',

#game__namewillbeaccessedasgame_name

'game_name',

)

ThePlayerScoreFilterisasubclassoftherest_framework.filters.FilterSetclass.WewanttocustomizesettingsforthefieldsthatwewilluseforfilteringinthePlayerScoreListclass-basedview,andtherefore,wecreatedthenewPlayerScoreFilterclass.Theclassdeclaresthefollowingsixclassattributes:

min_score:Itisadjango_filters.NumberFilterinstancethatallowstheclienttofiltertheplayerscoreswhosescorenumericvalueisgreaterthanorequaltothespecifiednumber.Thevaluefornameindicatesthefieldtowhichthenumericfilterisapplied,'score',andthelookup_exprvalueindicatesthelookupexpression,'gte',whichmeansgreaterthanorequalto.max_score:Itisadjango_filters.NumberFilterinstancethatallowstheclienttofiltertheplayerscoreswhosescorenumericvalueislessthanorequaltothespecifiednumber.Thevaluefornameindicatesthefieldtowhichthenumericfilterisapplied,'score',andthelookup_exprvalueindicatesthelookupexpression,'lte',whichmeanslessthanorequalto.from_score_date:Itisadjango_filters.DateTimeFilterinstancethatallowstheclienttofiltertheplayerscoreswhosescore_datedatetimevalueisgreaterthanorequaltothespecifieddatetimevalue.Thevaluefornameindicatesthefieldtowhichthedatetimefilterisapplied,'score_date',andthelookup_exprvalueindicatesthelookupexpression,'gte'.to_score_date:Itisadjango_filters.DateTimeFilterinstancethatallowstheclienttofiltertheplayerscoreswhosescore_datedatetimevalueislessthanorequaltothespecifieddatetimevalue.Thevaluefornameindicatesthefieldtowhichthedatetime

Page 181: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

filterisapplied,'score_date',andthelookup_exprvalueindicatesthelookupexpression,'lte'.player_name:Itisadjango_filters.AllValuesFilter:Itisaninstancethatallowstheclienttofiltertheplayerscoreswhoseplayer'snamematchesthespecifiedstringvalue.Thevaluefornameindicatesthefieldtowhichthefilterisapplied,'player__name'.Notethatthevaluehasadoubleunderscore(__)andyoucanreaditasthenamefieldfortheplayermodelorsimplyreplacethedoubleunderscorewithadotandreadplayer.name.ThenameusesDjango'sdoubleunderscoresyntax.However,wedon'twanttheclienttouseplayer__nametospecifythefilterfortheplayer'sname.Thus,theinstanceisstoredintheclassattributenamedplayer_name,withjustasingleunderscorebetweenplayerandname.ThebrowsableAPIwilldisplayadropdownwithallthepossiblevaluesfortheplayer'snametouseasafilter.Thedropdownwillonlyincludetheplayers'namesthathaveregisteredscoresbecauseweusedtheAllValuesFilterclass.game_name:Thisisadjango_filters.AllValuesFilterinstancethatallowstheclienttofiltertheplayerscoreswhosegame'snamematchesthespecifiedstringvalue.Thevaluefornameindicatesthefieldonwhichthefilterisapplied,'game__name'.ThenameusesthepreviouslyexplainedDjango'sdoubleunderscoresyntax.Ashappenedwithplayer_name,wedon'twanttheclienttousegame__nametospecifythefilterforthegame'sname,andtherefore,westoredtheinstanceintheclassattributenamedgame_name,withjustasingleunderscorebetweengameandname.ThebrowsableAPIwilldisplayadropdownwithallthepossiblevaluesforthegame'snametouseasafilter.Thedropdownwillonlyincludethegame'snamesthathaveregisteredscoresbecauseweusedtheAllValuesFilterclass.

Inaddition,thePlayerScoreFilterclassdeclaresaMetainnerclassthatdeclarestwoattributes:modelandfields.Themodelattributespecifiesthemodelrelatedtothefilterset,thatis,thePlayerScoreclass.Thefieldsattributespecifiesatupleofstringwhosevaluesindicatethefieldnamesandfilternamesthatwewanttoincludeinthefiltersfortherelatedmodel.Weincluded'scores'andthenamesforallthepreviouslydeclaredfilters.Thestring'scores'referstothescorefieldnameandwewanttoapplythedefaultnumericfilterthatwillbebuiltunderthehoodstoallowtheclienttofilterbyanexactmatchonthescorefield.

Finally,addthefollowinghighlightedlinestothePlayerScoreListclassdeclaredintheviews.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_04_02folder:

classPlayerScoreList(generics.ListCreateAPIView):

queryset=PlayerScore.objects.all()

serializer_class=PlayerScoreSerializer

name='playerscore-list'

filter_class=PlayerScoreFilter

ordering_fields=(

'score',

'score_date',

)

Thefilter_classattributespecifiestheFilterSetsubclassthatwewanttouseforthisclass-

Page 182: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

basedview:PlayerScoreFilter.Inaddition,wespecifiedthetwofieldnamesthattheclientwillbeabletousefororderingintheordering_fieldstupleofstring.

Page 183: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testingfiltering,searching,andorderingNow,wecanlaunchDjango'sdevelopmentservertocomposeandsendHTTPrequests.ExecuteanyofthefollowingtwocommandsbasedonyourneedstoaccesstheAPIinotherdevicesorcomputersconnectedtoyourLAN.RememberthatweanalyzedthedifferencebetweentheminChapter1,DevelopingRESTfulAPIswithDjango.

pythonmanage.pyrunserver

pythonmanage.pyrunserver0.0.0.0:8000

Afterwerunanyofthepreviouscommands,thedevelopmentserverwillstartlisteningatport8000:

Now,wewillcomposeandsendanHTTPrequesttoretrieveallthegamecategorieswhosenamematches3DRPG:

http:8000/game-categories/?name=3D+RPG

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8000/game-categories/?name=3D+RPG

Thefollowinglinesshowasampleresponsewiththesinglegamecategorywhosenamematchesthespecifiednameinthefilter.ThefollowinglinesonlyshowtheJSONbodywithouttheheaders:

{

"count":1,

"next":null,

"previous":null,

"results":[

{

"games":[

"http://localhost:8000/games/2/",

"http://localhost:8000/games/15/",

"http://localhost:8000/games/3/",

"http://localhost:8000/games/16/"

],

"name":"3DRPG",

"pk":3,

"url":"http://localhost:8000/game-categories/3/"

}

]

}

WewillcomposeandsendanHTTPrequesttoretrieveallthegameswhoserelatedcategoryidisequalto3andthevaluefortheplayedfieldisequaltoTrue.Wewanttosorttheresultsbyrelease_dateindescendingorder,andtherefore,wespecify-release_dateinthevalueforordering.Thehyphen(-)beforethefieldnamespecifiestheorderingfeaturetousedescendingorderinsteadofthedefaultascendingorder.Makesureyoureplace3withthepk

Page 184: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

valueofthepreviouslyretrievedgamecategorynamed3DRPG.Theplayedfieldisaboolfield,andtherefore,wehavetousePython-validboolvalues(TrueandFalse)whenspecifyingthedesiredvaluesfortheboolfieldinthefilter:

http':8000/games/?game_category=3&played=True&ordering=-release_date'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?game_category=3&played=True&ordering=-

release_date'

Thefollowinglinesshowasampleresponsewiththetwogamesthatmatchthespecifiedcriteriainthefilter.ThefollowinglinesonlyshowtheJSONbodywithouttheheaders:

{

"count":2,

"next":null,

"previous":null,

"results":[

{

"game_category":"3DRPG",

"name":"PvZGardenWarfare4",

"owner":"superuser",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/2/"

},

{

"game_category":"3DRPG",

"name":"SupermanvsAquaman",

"owner":"superuser",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/3/"

}

]

}

IntheGameListclass,wespecified'game_category'asoneofthestringsinthefilter_fieldstupleofstring.Thus,wehadtousethegamecategoryidinthefilter.Now,wewilluseafilteronthegame'snamerelatedtoaregisteredscore.ThePlayerScoreFilterclassprovidesusafiltertothenameoftherelatedgameingame_name.Wewillcombinethefilterwithanotherfilterontheplayer'snamerelatedtoaregisteredscore.ThePlayerScoreFilterclassprovidesusafiltertothenameoftherelatedplayerinplayer_name.Bothconditionsspecifiedinthecriteriamustbemet,andtherefore,thefiltersarecombinedwiththeANDoperator:

http':8000/player-scores/?player_name=Kevin&game_name=Superman+vs+Aquaman'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/player-scores/?

Page 185: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

player_name=Kevin&game_name=Superman+vs+Aquaman'

Thefollowinglinesshowasampleresponsewiththescorethatmatchesthespecifiedcriteriainthefilters.ThefollowinglinesonlyshowtheJSONbodywithouttheheaders:

{

"count":1,

"next":null,

"previous":null,

"results":[

{

"game":"SupermanvsAquaman",

"pk":5,

"player":"Kevin",

"score":123200,

"score_date":"2016-06-22T03:02:00.776594Z",

"url":"http://localhost:8000/player-scores/5/"

}

]

}

WewillcomposeandsendanHTTPrequesttoretrieveallthescoresthatmatchthefollowingcriteria.Theresultswillbeorderedbyscore_dateindescendingorder.

Thescorevalueisbetween30,000and150,000Thescore_dateisbetween2016-06-21and2016-06-22

http':8000/player-scores/?score=&from_score_date=2016-06-

01&to_score_date=2016-06-28&min_score=30000&max_score=150000&ordering=-

score_date'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/player-scores/?score=&from_score_date=2016-06-

01&to_score_date=2016-06-28&min_score=30000&max_score=150000&ordering=-

score_date'

Thefollowinglinesshowasampleresponsewiththethreegamesthatmatchthespecifiedcriteriainthefilters.Weoverrodethedefaultorderingspecifiedinthemodelwiththespecifiedorderingintherequest.ThefollowinglinesonlyshowtheJSONbodywithouttheheaders:

{

"count":3,

"next":null,

"previous":null,

"results":[

{

"game":"SupermanvsAquaman",

"pk":5,

"player":"Kevin",

"score":123200,

"score_date":"2016-06-22T03:02:00.776594Z",

"url":"http://localhost:8000/player-scores/5/"

Page 186: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

},

{

"game":"PvZGardenWarfare4",

"pk":4,

"player":"Brandon",

"score":85125,

"score_date":"2016-06-22T01:02:00.776594Z",

"url":"http://localhost:8000/player-scores/4/"

},

{

"game":"PvZGardenWarfare4",

"pk":3,

"player":"Brandon",

"score":35000,

"score_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/player-scores/3/"

}

]

}

Tip

Intheprecedingrequests,alltheresponsesdidn'thavemorethanonepage.Incasetheresponserequiresmorethanonepage,thevaluesforthepreviousandnextkeyswilldisplaytheURLsthatincludethecombinationofthefilters,search,orderingandpagination.

WewillcomposeandsendanHTTPrequesttoretrieveallthegameswhosenamestartswith'S'.Wewillusethesearchfeaturethatweconfiguredtorestrictthesearchbehaviortoastarts-withmatchonthenamefield:

http':8000/games/?search=S'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':8000/games/?search=S'

Thefollowinglinesshowasampleresponsewiththetwogamesthatmatchthespecifiedsearchcriteria,thatis,thosegameswhosenamestartswith'S'.ThefollowinglinesonlyshowtheJSONbodywithouttheheaders:

{

"count":2,

"next":null,

"previous":null,

"results":[

{

"game_category":"2Dmobilearcade",

"name":"ScribblenautsUnlimited",

"owner":"superuser",

"played":false,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/7/"

},

Page 187: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

{

"game_category":"3DRPG",

"name":"SupermanvsAquaman",

"owner":"superuser",

"played":true,

"release_date":"2016-06-21T03:02:00.776594Z",

"url":"http://localhost:8000/games/3/"

}

]

}

Tip

Wecanchangethesearchandorderingparameter'sdefaultnames:'search'and'ordering'.WejustneedtospecifythedesirednamesintheSEARCH_PARAMandtheORDERING_PARAMsettings.

Page 188: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Filtering,searching,andorderingintheBrowsableAPIWecantakeadvantageofthebrowsableAPItoeasilytestfilter,search,andorderfeaturesthroughawebbrowser.Openawebbrowserandenterhttp://localhost:8000/player-scores/.Incaseyouuseanothercomputerordevicetorunthebrowser,replacelocalhostwiththeIPofthecomputerthatisrunningtheDjangodevelopmentserver.ThebrowsableAPIwillcomposeandsendaGETrequestto/player-scores/andwilldisplaytheresultsofitsexecution,thatis,theheadersandtheJSONplayerscoreslist.YouwillnoticethatthereisanewFiltersbuttonlocatedontheleft-handsideoftheOPTIONSbutton.

ClickonFiltersandthebrowsableAPIwilldisplaytheFiltersdialogboxwiththeappropriatecontrolsforeachfilterthatyoucanapplybelowFieldFiltersandthedifferentorderingoptionsbelowOrdering.ThefollowingscreenshotshowstheFiltersdialogbox:

Page 189: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BoththePlayernameandGamenamedropdownswillonlyincludetherelatedplayer'sandgame'snamesthathaveregisteredscoresbecauseweusedtheAllValuesFilterclassforbothfilters.Afterweenterallthevaluesforthefilters,wecanselectthedesiredorderingoptionorclickSubmit.ThebrowsableAPIwillcomposeandsendtheappropriateHTTPrequestand

Page 190: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

willrenderawebpagewiththeresultsofitsexecution.TheresultswillincludetheHTTPrequestthatwasmadetotheDjangoserver.Thefollowingscreenshotshowsanexampleoftheresultofexecutingthenextrequest,thatis,therequestwebuiltusingthebrowsableAPI:

GET/player-scores/?

score=&from_score_date=&to_score_date=&min_score=30000&max_score=40000&player

_name=Brandon&game_name=PvZ+Garden+Warfare+4

Page 191: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupunittestsFirst,wewillinstallthecoverageanddjango-nosepackagesinourvirtualenvironment.Wewillmakethenecessaryconfigurationstousethedjango_nose.NoseTestRunnerclasstorunallthetestswecodeandwewillusethenecessaryconfigurationstoimprovetheaccuracyofthetestcoveragemeasurements.

MakesurethatyouquitDjango'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheterminalortheCommandPromptwindowinwhichitisrunning.Wejustneedtorunthefollowingcommandtoinstallthecoveragepackage:

pipinstallcoverage

Thelastfewlinesoftheoutputindicatethatthedjango-nosepackagehasbeensuccessfullyinstalled:

Collectingcoverage

Downloadingcoverage-4.1.tar.gz

Installingcollectedpackages:coverage

Runningsetup.pyinstallforcoverage

Successfullyinstalledcoverage-4.1

Wejustneedtorunthefollowingcommandtoinstallthedjango-nosepackage:

pipinstalldjango-nose

Thelastfewlinesoftheoutputindicatethatthedjango-nosepackagehasbeensuccessfullyinstalled.

Collectingdjango-nose

Downloadingdjango_nose-1.4.4-py2.py3-none-any.whl

Collectingnose>=1.2.1(fromdjango-nose)

Downloadingnose-1.3.7-py3-none-any.whl

Installingcollectedpackages:nose,django-nose

Successfullyinstalleddjango-nose-1.4.4nose-1.3.7

Add'django_nose'totheinstalledappsinthesettings.pyfile,specifically,totheINSTALLED_APPSstringlist.Thefollowingcodeshowsthelinesweneedtoaddashighlightedcode.Thecodefileforthesampleisincludedintherestful_python_chapter_04_03folder:

INSTALLED_APPS=[

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

#DjangoRESTFramework

'rest_framework',

#Gamesapplication

'games.apps.GamesConfig',

Page 192: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

#Crispyforms

'crispy_forms',

#Djangonose

'django_nose',

]

Openthegamesapi/settings.pyfileandaddthefollowinglinestoconfigurethedjango_nose.NoseTestRunnerclassasourtestrunnerandspecifythedefaultcommand-lineoptionsthatwewillusewhenwerunourtests.Thecodefileforthesampleisincludedintherestful_python_chapter_04_03folder:

#Wewanttousenosetorunallthetests

TEST_RUNNER='django_nose.NoseTestSuiteRunner'

#Wewantnosetomeasurecoverageonthegamesapp

NOSE_ARGS=[

'--with-coverage',

'--cover-erase',

'--cover-inclusive',

'--cover-package=games',

]

TheNOSE_ARGSsettingsspecifythefollowingcommand-lineoptionsforthenosetestsuiterunnerandforcoverage:

--with-coverage:Thisoptionspecifiesthatwealwayswanttogenerateatestcoveragereport.--cover-erase:Thisoptionmakessurethethetestrunnerdeletesthecoveragetestresultsfromthepreviousrun.--cover-inclusive:ThisoptionincludesallthePythonfilesundertheworkingdirectoryinthecoveragereport.Thisway,wemakesurethatwediscoverholesintestcoveragewhenwedon'timportallthefilesinourtestsuite.Wewillcreateatestsuitethatwon'timportallthefiles,andtherefore,thisoptionisveryimportanttohaveanaccuratetestcoveragereport.--cover-package=games:Thisoptionindicatesthemodulethatwewanttocover:games.

Finally,createanewtextfilenamed.coveragercwithinthegamesapirootfolderwiththefollowingcontent:

[run]

omit=*migrations*

Thisway,thecoverageutilitywon'ttakeintoaccountmanythingsrelatedtothegeneratedmigrationswhenprovidinguswiththetestcoveragereport.Wewillhaveamoreaccuratetestcoveragereportwiththissettingsfile.

Page 193: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WritingafirstroundofunittestsNow,wewillwritethefirstroundofunittests.Specifically,wewillwriteunittestsrelatedtothegamecategoryclass-basedviews:GameCategoryListandGameCategoryDetail.Opentheexistinggames/test.pyfileandreplacetheexistingcodewiththefollowinglinesthatdeclaremanyimportstatementsandtheGameCategoryTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_04_04folder,asshown:

fromdjango.testimportTestCase

fromdjango.core.urlresolversimportreverse

fromdjango.utils.httpimporturlencode

fromrest_frameworkimportstatus

fromrest_framework.testimportAPITestCase

fromgames.modelsimportGameCategory

classGameCategoryTests(APITestCase):

defcreate_game_category(self,name):

url=reverse('gamecategory-list')

data={'name':name}

response=self.client.post(url,data,format='json')

returnresponse

deftest_create_and_retrieve_game_category(self):

"""

EnsurewecancreateanewGameCategoryandthenretrieveit

"""

new_game_category_name='NewGameCategory'

response=self.create_game_category(new_game_category_name)

self.assertEqual(response.status_code,status.HTTP_201_CREATED)

self.assertEqual(GameCategory.objects.count(),1)

self.assertEqual(

GameCategory.objects.get().name,

new_game_category_name)

print("PK{0}".format(GameCategory.objects.get().pk))

TheGameCategoryTestsclassisasubclassofrest_framework.test.APITestCase.Theclassdeclaresthecreate_game_categorymethodthatreceivesthedesirednameforthenewgamecategoryasanargument.ThemethodbuildstheURLandthedatadictionarytocomposeandsendanHTTPPOSTmethodtotheviewassociatedwiththegamecategory-listviewnameandreturnstheresponsegeneratedbythisrequest.Thecodeusesself.clienttoaccesstheAPIClientinstancethatallowsustoeasilycomposeandsendHTTPrequestsfortesting.Inthiscase,thecodecallsthepostmethodwiththebuilturl,thedatadictionary,andthedesiredformatforthedata-'json'.Manytestmethodswillcallthecreate_game_categorymethodtocreateagamecategoryandthencomposeandsendotherHTTPrequeststotheAPI.

Thetest_create_and_retrieve_game_categorymethodtestswhetherwecancreateanewGameCategoryandthenretrieveit.Themethodcallsthecreate_game_categorymethodexplainedearlierandthenusesassertEqualtocheckforthefollowingexpectedresults:

Page 194: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thestatus_codefortheresponseisHTTP201Created(status.HTTP_201_CREATED)ThetotalnumberofGameCategoryobjectsretrievedfromthedatabaseis1

AddthefollowingmethodstotheGameCategoryTestsclasswecreatedinthegames/test.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_04_04folder:

deftest_create_duplicated_game_category(self):

"""

EnsurewecancreateanewGameCategory.

"""

url=reverse('gamecategory-list')

new_game_category_name='NewGameCategory'

data={'name':new_game_category_name}

response1=self.create_game_category(new_game_category_name)

self.assertEqual(

response1.status_code,

status.HTTP_201_CREATED)

response2=self.create_game_category(new_game_category_name)

self.assertEqual(

response2.status_code,

status.HTTP_400_BAD_REQUEST)

deftest_retrieve_game_categories_list(self):

"""

Ensurewecanretrieveagamecagory

"""

new_game_category_name='NewGameCategory'

self.create_game_category(new_game_category_name)

url=reverse('gamecategory-list')

response=self.client.get(url,format='json')

self.assertEqual(

response.status_code,

status.HTTP_200_OK)

self.assertEqual(

response.data['count'],

1)

self.assertEqual(

response.data['results'][0]['name'],

new_game_category_name)

deftest_update_game_category(self):

"""

Ensurewecanupdateasinglefieldforagamecategory

"""

new_game_category_name='InitialName'

response=self.create_game_category(new_game_category_name)

url=reverse(

'gamecategory-detail',

None,

{response.data['pk']})

updated_game_category_name='UpdatedGameCategoryName'

data={'name':updated_game_category_name}

patch_response=self.client.patch(url,data,format='json')

self.assertEqual(

patch_response.status_code,

Page 195: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

status.HTTP_200_OK)

self.assertEqual(

patch_response.data['name'],

updated_game_category_name)

deftest_filter_game_category_by_name(self):

"""

Ensurewecanfilteragamecategorybyname

"""

game_category_name1='Firstgamecategoryname'

self.create_game_category(game_category_name1)

game_caregory_name2='Secondgamecategoryname'

self.create_game_category(game_caregory_name2)

filter_by_name={'name':game_category_name1}

url='{0}?{1}'.format(

reverse('gamecategory-list'),

urlencode(filter_by_name))

response=self.client.get(url,format='json')

self.assertEqual(

response.status_code,

status.HTTP_200_OK)

self.assertEqual(

response.data['count'],

1)

self.assertEqual(

response.data['results'][0]['name'],

game_category_name1)

Weaddedthefollowingmethodsthatstartwhosenamestartwiththetest_prefix:

test_create_duplicated_game_category:Testswhethertheuniqueconstraintsdon'tmakeitpossibleforustocreatetwogamecategorieswiththesamename.ThesecondtimewecomposeandsendanHTTPPOSTrequestwithaduplicatecategoryname,wemustreceiveanHTTP400BadRequeststatuscode(status.HTTP_400_BAD_REQUEST)test_retrieve_game_categories_list:Testswhetherwecanretrieveaspecificgamecategorybyitsprimarykeyoridtest_update_game_category:Testswhetherwecanupdateasinglefieldforagamecategorytest_filter_game_category_by_name:Testswhetherwecanfilteragamecategorybyname

Tip

Notethateachtestthatrequiresaspecificconditioninthedatabasemustexecuteallthenecessarycodeforthedatabasetobeinthisspecificcondition.Forexample,inordertoupdateanexistinggamecategory,firstwemustcreateanewgamecategoryandthenwecanupdateit.Eachtestmethodwillbeexecutedwithoutdatafromthepreviouslyexecutedtestmethodsinthedatabase,thatis,eachtestwillrunwithadatabasecleanedofdatafromprevioustests.

ThelastthreemethodsintheprecedinglistcheckthedataincludedintheresponseJSON

Page 196: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

bodybyinspectingthedataattributefortheresponse.Forexample,thefirstlinecheckswhetherthevalueforcountisequalto1andthenextlinescheckwhetherthenamekeyforthefirstelementintheresultsarrayisequaltothevalueholdinthenew_game_category_namevariable:

self.assertEqual(response.data['count'],1)

self.assertEqual(

response.data['results'][0]['name'],

new_game_category_name)

Thetest_filter_game_category_by_namemethodcallsthedjango.utils.http.urlencodefunctiontogenerateanencodedURLfromthefilter_by_namedictionarythatspecifiesthefieldnameandthevaluewewanttousetofiltertheretrieveddata.ThefollowinglinesshowthecodethatgeneratestheURLandsavesitintheurlvariable.Ifgame_cagory_name1is'Firstgamecategoryname',theresultofthecalltotheurlencodefunctionwillbe'name=First+game+category+name'.

filter_by_name={'name':game_category_name1}

url='{0}?{1}'.format(

reverse('gamecategory-list'),

urlencode(filter_by_name))

Page 197: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

RunningunittestsandcheckingtestingcoverageNow,runthefollowingcommandtocreateatestdatabase,runallthemigrationsandusetheDjangonosetestrunningtoexecuteallthetestswecreated.ThetestrunnerwillexecuteallthemethodsforourGameCategoryTestsclassthatstartwiththetest_prefixandwilldisplaytheresults.

Tip

Thetestswon'tmakechangestothedatabasewehavebeenusingwhenworkingontheAPI.

Rememberthatweconfiguredmanydefaultcommand-lineoptionsthatwillbeusedwithouttheneedtoentertheminourcommand-line.Runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing.Wewillusethe-v2optiontousetheverbositylevel2becausewewanttocheckallthethingsthatthetestrunnerisdoing:

pythonmanage.pytest-v2

Thefollowinglinesshowthesampleoutput:

nosetests--with-coverage--cover-package=games--cover-erase--cover-

inclusive-v--verbosity=2

Creatingtestdatabaseforalias'default'('test_games')...

Operationstoperform:

Synchronizeunmigratedapps:django_nose,staticfiles,crispy_forms,

messages,rest_framework

Applyallmigrations:games,admin,auth,contenttypes,sessions

Synchronizingappswithoutmigrations:

Creatingtables...

RunningdeferredSQL...

Runningmigrations:

Renderingmodelstates...DONE

Applyingcontenttypes.0001_initial...OK

Applyingauth.0001_initial...OK

Applyingadmin.0001_initial...OK

Applyingadmin.0002_logentry_remove_auto_add...OK

Applyingcontenttypes.0002_remove_content_type_name...OK

Applyingauth.0002_alter_permission_name_max_length...OK

Applyingauth.0003_alter_user_email_max_length...OK

Applyingauth.0004_alter_user_username_opts...OK

Applyingauth.0005_alter_user_last_login_null...OK

Applyingauth.0006_require_contenttypes_0002...OK

Applyingauth.0007_alter_validators_add_error_messages...OK

Applyinggames.0001_initial...OK

Applyinggames.0002_auto_20160623_2131...OK

Applyinggames.0003_game_owner...OK

Applyingsessions.0001_initial...OK

EnsurewecancreateanewGameCategoryandthenretrieveit...ok

EnsurewecancreateanewGameCategory....ok

Page 198: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Ensurewecanfilteragamecategorybyname...ok

Ensurewecanretrieveagamecagory...ok

Ensurewecanupdateasinglefieldforagamecategory...ok

NameStmtsMissCover

------------------------------------------

games.py00100%

games/admin.py110%

games/apps.py330%

games/models.py36353%

games/pagination.py30100%

games/permissions.py6350%

games/serializers.py450100%

games/urls.py30100%

games/views.py91298%

------------------------------------------

TOTAL1884477%

------------------------------------------

Ran5testsin0.143s

OK

Destroyingtestdatabaseforalias'default'('test_games')...

Theoutputprovidesthedetailsindicatingthatthetestrunnerexecuted5testsandallofthempassed.Afterthedetailsaboutthemigrationsareexecuted,theoutputdisplaysthecommentsweincludedforeachmethodintheGameCategoryTestsclassthatstartedwiththetest_prefixandrepresentedatesttobeexecuted.Thefollowinglistshowsthedescriptionincludedinthecommentsandthemethodthattheyrepresent:

EnsureswecancreateanewGameCategoryandthenretrieveit:test_create_and_retrieve_game_category.EnsureswecancreateanewGameCategory:test_create_duplicated_game_category.Ensureswecanfilteragamecategorybyname:test_retrieve_game_categories_list.Ensureswecanretrieveagamecagory:test_update_game_category.Ensureswecanupdateasinglefieldforagamecategory:test_filter_game_category_by_name.

ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageusesthecodeanalysistoolsandthetracinghooksincludedinthePythonstandardlibrarytodeterminewhichlinesofcodeareexecutableandwhichoftheselineshavebeenexecuted.Thereportprovidesatablewiththefollowingcolumns:

Name:ThePythonmodulename.Stmts:ThecountofexecutablestatementsforthePythonmodule.Miss:Thenumberofexecutablestatementsmissed,thatis,theonesthatweren'texecuted.Cover:Thecoverageofexecutablestatements,expressedasapercentage.

Wedefinitelyhaveaverylowcoverageformodels.pybasedonthemeasurementsshowninthereport.Infact,wejustwroteafewtestsrelatedtotheGameCategorymodel,andtherefore,itmakessensethatthecoverageisreallylowforthemodels:

Wecanrunthecoveragecommandwiththe-mcommand-lineoptiontodisplaytheline

Page 199: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

numbersofthemissingstatementsinanewMissingcolumn.

coveragereport-m

Thecommandwillusetheinformationfromthelastexecutionandwilldisplaythemissingstatements.Thenextlinesshowasampleoutputthatcorrespondtothepreviousexecutionoftheunittests:

NameStmtsMissCoverMissing

----------------------------------------------------

games/__init__.py00100%

games/admin.py110%1

games/apps.py330%1-5

games/models.py36353%1-10,14-70

games/pagination.py30100%

games/permissions.py6350%6-9

games/serializers.py450100%

games/tests.py550100%

games/urls.py30100%

games/views.py91298%83,177

----------------------------------------------------

TOTAL2434482%

Now,runthefollowingcommandtogetannotatedHTMLlistingsdetailingmissedlines:

coveragehtml

Opentheindex.htmlHTMLfilegeneratedinthehtmlcovfolderwithyourwebbrowser.ThefollowingpictureshowsanexamplereportthatcoveragegeneratedinHTMLformat.

Page 200: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Clickortapongames/models.pyandthewebbrowserwillrenderawebpagethatdisplaysthestatementsthatwererun,themissingonesandtheexcluded,withdifferentcolors.Wecanclickortapontherun,missing,andexcludedbuttonstoshoworhidethebackgroundcolorthatrepresentsthestatusforeachlineofcode.Bydefault,themissinglinesofcodewillbedisplayedwithapinkbackground.Thus,wemustwriteunitteststhattargettheselinesofcodetoimproveourtestscoverage:

Page 201: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select
Page 202: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ImprovingtestingcoverageNow,wewillwriteadditionalunitteststoimprovethetestingcoverage.Specifically,wewillwriteunittestsrelatedtotheplayerclassbasedviews:PlayerListandPlayerDetail.Opentheexistinggames/test.pyfileandinsertthefollowinglinesafterthelastlinethatdeclaresimports.WeneedanewimportstatementandwewilldeclarethenewPlayerTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_04_05folder:

fromgames.modelsimportPlayer

classPlayerTests(APITestCase):

defcreate_player(self,name,gender):

url=reverse('player-list')

data={'name':name,'gender':gender}

response=self.client.post(url,data,format='json')

returnresponse

deftest_create_and_retrieve_player(self):

"""

EnsurewecancreateanewPlayerandthenretrieveit

"""

new_player_name='NewPlayer'

new_player_gender=Player.MALE

response=self.create_player(new_player_name,new_player_gender)

self.assertEqual(response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Player.objects.count(),1)

self.assertEqual(

Player.objects.get().name,

new_player_name)

deftest_create_duplicated_player(self):

"""

EnsurewecancreateanewPlayerandwecannotcreateaduplicate.

"""

url=reverse('player-list')

new_player_name='NewFemalePlayer'

new_player_gender=Player.FEMALE

response1=self.create_player(new_player_name,new_player_gender)

self.assertEqual(

response1.status_code,

status.HTTP_201_CREATED)

response2=self.create_player(new_player_name,new_player_gender)

self.assertEqual(

response2.status_code,

status.HTTP_400_BAD_REQUEST)

deftest_retrieve_players_list(self):

"""

Ensurewecanretrieveaplayer

"""

new_player_name='NewFemalePlayer'

new_player_gender=Player.FEMALE

self.create_player(new_player_name,new_player_gender)

url=reverse('player-list')

Page 203: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

response=self.client.get(url,format='json')

self.assertEqual(

response.status_code,

status.HTTP_200_OK)

self.assertEqual(

response.data['count'],

1)

self.assertEqual(

response.data['results'][0]['name'],

new_player_name)

self.assertEqual(

response.data['results'][0]['gender'],

new_player_gender)

ThePlayerTestsclassisasubclassofrest_framework.test.APITestCase.Theclassdeclaresthecreate_playermethodthatreceivesthedesirednameandgenderforthenewplayerasarguments.ThemethodbuildstheurlandthedatadictionarytocomposeandsendanHTTPPOSTmethodtotheviewassociatedwiththeplayer-listviewnameandreturnstheresponsegeneratedbythisrequest.Manytestmethodswillcallthecreate_playermethodtocreateaplayerandthencomposeandsendotherHTTPrequeststotheAPI.

Theclassdeclaresthefollowingmethodsthatstartwhosenamestartwiththetest_prefix:

test_create_and_retrieve_player:TestswhetherwecancreateanewPlayerandthenretrieveit.test_create_duplicated_player:Testswhethertheuniqueconstraintsdon'tmakeitpossibleforustocreatetwoplayerswiththesamename.ThesecondtimewecomposeandsendanHTTPPOSTrequestwithaduplicateplayername,wemustreceiveanHTTP400BadRequeststatuscode(status.HTTP_400_BAD_REQUEST).test_retrieve_player_list:Testswhetherwecanretrieveaspecificgamecategorybyitsprimarykeyorid.

Wejustcodedafewtestsrelatedtoplayerstoimprovetestcoverageandnoticetheimpactonthetestcoveragereport.

Now,runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing.Wewillusethe-v2optiontousetheverbositylevel2becausewewanttocheckallthethingsthatthetestrunnerisdoing:

pythonmanage.pytest-v2

Thefollowinglinesshowthelastlinesofthesampleoutput:

EnsurewecancreateanewGameCategoryandthenretrieveit...ok

EnsurewecancreateanewGameCategory....ok

Ensurewecanfilteragamecategorybyname...ok

Ensurewecanretrieveagamecagory...ok

Ensurewecanupdateasinglefieldforagamecategory...ok

EnsurewecancreateanewPlayerandthenretrieveit...ok

EnsurewecancreateanewPlayerandwecannotcreateaduplicate....ok

Ensurewecanretrieveaplayer...ok

Page 204: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

NameStmtsMissCover

------------------------------------------

games.py00100%

games/admin.py110%

games/apps.py330%

games/models.py36346%

games/pagination.py30100%

games/permissions.py6350%

games/serializers.py450100%

games/urls.py30100%

games/views.py91298%

------------------------------------------

TOTAL1884377%

----------------------------------------------------------------------

Ran8testsin0.168s

OK

Destroyingtestdatabaseforalias'default'('test_games')...

Theoutputprovidesdetailsthatindicatethatthetestrunnerexecuted8testsandallofthempassed.ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageincreasedtheCoverpercentagefrom3%inthepreviousrunto6%.TheadditionaltestswewroteexecutecodeforthePlayermodel,andtherefore,thereisanimpactinthecoveragereport.

Tip

Wejustcreatedafewunitteststounderstandhowwecancodethem.However,ofcourse,itwouldbenecessarytowritemoreteststoprovideanappropriatecoverageofallthefeaturedandexecutionscenariosincludedintheAPI.

Page 205: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingstrategiesfordeploymentsandscalabilityOneofthebiggestdrawbacksrelatedtoDjangoandDjangoRESTFrameworkisthateachHTTPrequestisblocking.Thus,whenevertheDjangoserverreceivesanHTTPrequest,itdoesn'tstartworkingonanyotherHTTPrequestsintheincomingqueueuntiltheserversendstheresponseforthefirstHTTPrequestitreceived.

However,oneofthegreatestadvantagesofRESTfulWebServicesisthattheyarestateless,thatis,theyshouldn'tkeepaclientstateonanyserver.OurAPIisagoodexampleofastatelessRESTfulWebService.Thus,wecanmaketheAPIrunonasmanyserversasnecessarytoachieveourscalabilitygoals.Obviously,wemusttakeintoaccountthatwecaneasilytransformthedatabaseserverinourscalabilitybottleneck.

Tip

Nowadays,wehaveahugenumberofcloud-basedalternativestodeployaRESTfulwebservicethatusesDjangoandDjangoRESTFrameworkandmakeitextremelyscalable.Justtomentionafewexamples,wehaveHeroku,PythonAnywhere,GoogleAppEngine,OpenShift,AWSElasticBeanstalk,andWindowsAzure.

Eachplatformincludesdetailedinstructionstodeployourapplication.Allofthemwillrequireustogeneratetherequirements.txtfilethatliststheapplicationdependenciestogetherwiththeirversions.Thisway,theplatformswillbeabletoinstallallthenecessarydependencieslistedinthefile.

Runthefollowingpipfreeze,togeneratetherequirements.txtfile:

pipfreeze>requirements.txt

Thefollowinglinesshowthecontentsofasamplegeneratedrequirements.txtfile.However,bearinmindthatmanypackagesincreasetheirversionnumberquicklyandyoumightseedifferentversionsinyourconfiguration:

coverage==4.1

Django==1.9.7

django-braces==1.9.0

django-crispy-forms==1.6.0

django-filter==0.13.0

django-nose==1.4.4

django-oauth-toolkit==0.10.0

djangorestframework==3.3.3

nose==1.3.7

oauthlib==1.0.3

psycopg2==2.6.2

six==1.10.0

Page 206: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WealwayshavetomakesurethatweprofiletheAPIandthedatabasebeforewedeployourfirstversionoftheRESTfulWebService.Itisveryimportanttomakesurethatthegeneratedqueriesrunproperlyontheunderlyingdatabaseandthatthemostpopularqueriesdonotendupinsequentialscans.Itisusuallynecessarytoaddtheappropriateindexestothetablesinthedatabase.

WehavebeenusingbasicHTTPauthentication.Incasewedecidetousethisauthenticationorothermechanisms,wemustmakesurethattheAPIrunsunderHTTPSinproductionenvironments.Inaddition,wemustmakesurethatwechangethefollowinglineinthesettings.pyfile:

DEBUG=True

Wemustalwaysturnoffthedebugmodeinproduction,andtherefore,wemustreplacethepreviouslinewiththefollowingone:

DEBUG=False

Page 207: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. TheScopedRateThrottleclass:

1. Limitstherateofrequeststhataspecificusercanmake.2. LimitstherateofrequestsforspecificpartsoftheAPIidentifiedwiththevalue

assignedtothethrottle_scopeproperty.3. Limitstherateofrequeststhatananonymoususercanmake.

2. TheUserRateThrottleclass:1. Limitstherateofrequeststhataspecificusercanmake.2. LimitstherateofrequestsforspecificpartsoftheAPIidentifiedwiththevalue

assignedtothethrottle_scopeproperty.3. Limitstherateofrequeststhatananonymoususercanmake.

3. TheDjangoFilterBackendclass:1. Providessinglequeryparameterbasedsearchingcapabilitiesanditisbasedonthe

Djangoadmin'ssearchfunction.2. Allowstheclienttocontrolhowtheresultsareorderedwithasinglequery

parameter.3. Providesfieldfilteringcapabilities.

4. TheSearchFilterclass:1. Providessinglequeryparameterbasedsearchingcapabilitiesanditisbasedonthe

Djangoadmin'ssearchfunction.2. Allowstheclienttocontrolhowtheresultsareorderedwithasinglequery

parameter.3. Providesfieldfilteringcapabilities.

5. InasubclassofAPITestCase,self.clientis:1. TheAPIClientinstancethatallowsustoeasilycomposeandsendHTTPrequests

fortesting.2. TheAPITestClientinstancethatallowsustoeasilycomposeandsendHTTP

requestsfortesting.3. TheAPITestCaseinstancethatallowsustoeasilycomposeandsendHTTPrequests

fortesting.

Page 208: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wetookadvantageofthefeaturesincludedinDjangoRESTFrameworktodefinethrottlingpolicies.Weusedfiltering,searching,andorderingclassestomakeiteasytoconfigurefilters,searchqueries,anddesiredorderfortheresultsinHTTPrequests.WeusedthebrowsableAPIfeaturetotestthesenewfeaturesincludedinourAPI.

Wewrotethefirstroundofunittests,measuredtestcoverage,andthenwewroteadditionalunitteststoimprovetestcoverage.Finally,weunderstoodmanyconsiderationsfordeploymentandscalability.

NowthatwebuiltacomplexAPIwithDjangoRESTFrameworkandtestedit,wewillmovetoanotherpopularPythonwebframework,Flask,whichiswhatwearegoingtodiscussinthenextchapter.

Page 209: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter5.DevelopingRESTfulAPIswithFlaskInthischapter,wewillstartworkingwithFlaskanditsFlask-RESTfulextension;wewillalsocreateaRESTfulWebAPIthatperformsCRUDoperationsonasimplelist.Wewill:

DesignaRESTfulAPIthatperformsCRUDoperationsinFlaskwiththeFlask-RESTfulextensionUnderstandthetasksperformedbyeachHTTPmethodSetupthevirtualenvironmentwithFlaskanditsFlask-RESTfulextensionDeclarestatuscodesfortheresponsesCreatethemodeltorepresentaresourceUseadictionaryasarepositoryConfigureoutputfieldsforserializedresponsesWorkwithresourcefulroutingontopofFlaskpluggableviewsConfigureresourceroutingandendpointsMakeHTTPrequeststotheFlaskAPIWorkwithcommand-linetoolstointeractwiththeFlaskAPIWorkwithGUItoolstointeractwiththeFlaskAPI

Page 210: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DesigningaRESTfulAPItointeractwithasimpledatasourceImaginethatwehavetoconfigurethemessagestobedisplayedinanOLEDdisplaywiredtoanIoT (InternetofThings)device,theIoTdeviceiscapableofrunningPython3.5,Flask,andotherPythonpackages.ThereisateamthatiswritingcodethatretrievesstringmessagesfromadictionaryanddisplaysthemintheOLEDdisplaywiredtotheIoTdevice.WehavetostartworkingonamobileappandawebsitethathastointeractwithaRESTfulAPItoperformCRUDoperationswithstringmessages.

Wedon'tneedanORMbecausewewon'tpersistthestringmessagesonadatabase.Wewilljustworkwithanin-memorydictionaryasourdatasource.ItisoneoftherequirementsforthisRESTfulAPI.Inthiscase,theRESTfulwebservicewillberunningontheIoTdevice,thatis,wewillruntheFlaskdevelopmentserverontheIoTdevice.

Tip

WewilldefinitelylosescalabilityforourRESTfulAPIbecausewehavethein-memorydatasourceintheserver,andtherefore,wecannotruntheRESTfulAPIinanotherIoTdevice.However,wewillworkwithanotherexamplerelatedtoamorecomplexdatasourcethatwillbeabletoscaleintheRESTfulwaylater.ThefirstexampleisgoingtoallowustounderstandhowFlaskandFlask-RESTfulworktogetherwithaverysimplein-memorydatasource.

WehavechosenFlaskbecauseitismorelightweightthanDjango,wedon'tneedtoconfigureanORMandwewanttostartrunningtheRESTfulAPIontheIoTdevice,assoonaspossible,toallowalltheteamstointeractwithit.WewillcodethewebsitewithFlasktoo,andtherefore,wewanttousethesamewebmicro-frameworktopowerthewebsiteandtheRESTfulwebservice.

TherearemanyextensionsavailableforFlaskthatmakesiteasiertoperformspecifictaskswiththeFlaskmicro-framework.WewilltakeadvantageofFlask-RESTful,anextensionthatwillallowustoencouragebestpracticeswhilebuildingourRESTfulAPI.Inthiscase,wewillworkwithaPythondictionaryasthedatasource.Aspreviouslyexplained,wewillworkwithmorecomplexdatasourcesintheforthcomingexamples.

First,wemustspecifytherequirementsforourmainresource:amessage.Weneedthefollowingattributesorfieldsforamessage:

AnintegeridentifierAstringmessageAdurationinsecondsthatindicatesthetimethemessagehastobeprintedontheOLEDdisplayAcreationdateandtime-thetimestampwillbeaddedautomaticallywhenaddinganewmessagetothecollection

Page 211: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Amessagecategorydescription,suchas"Warning"and"Information"AnintegercounterthatindicatesthetimesthemessagehasbeenprintedintheOLEDdisplayAboolvalueindicatingwhetherthemessagewasprintedatleastonceontheOLEDdisplay

ThefollowingtableshowstheHTTPverbs,thescope,andthesemanticsforthemethodsthatourfirstversionoftheAPImustsupport.EachmethodiscomposedbyanHTTPverbandascopeandallthemethodshaveawell-definedmeaningforallthemessagesandcollections.InourAPI,eachmessagehasitsownuniqueURL.

HTTPverb Scope Semantics

GETCollectionofmessages

Retrieveallthestoredmessagesinthecollection,sortedbytheirnameinascendingorder

GET Message Retrieveasinglemessage

POSTCollectionofmessages Createanewmessageinthecollection

PATCH Message Updateafieldforanexistingmessage

DELETE Message Deleteanexistingmessage

Page 212: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthetasksperformedbyeachHTTPmethodLet'sconsiderthathttp://localhost:5000/api/messages/istheURLforthecollectionofmessages.IfweaddanumbertotheprecedingURL,weidentifyaspecificmessagewhoseidisequaltothespecifiednumericvalue.Forexample,http://localhost:5000/api/messsages/6identifiesthemessagewhoseidisequalto6.

Tip

WewantourAPItobeabletodifferentiatecollectionsfromasingleresourceofthecollectionintheURLs.Whenwereferacollection,wewilluseaslash(/)asthelastcharacterfortheURL,asinhttp://localhost:5000/api/messages/.Whenwerefertoasingleresourceofthecollectionwewon'tuseaslash(/)asthelastcharacterfortheURL,asinhttp://localhost:5000/api/messages/6.

WehavetocomposeandsendanHTTPrequestwiththePOSTHTTPverbandthehttp://localhost:5000/api/messages/requestURLtocreateanewmessage.Inaddition,wehavetoprovidetheJSONkey-valuepairswiththefieldnamesandthevaluestocreatethenewmessage.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefields,makesurethatitisavalidmessage,andpersistitinthemessagesdictionary.

Theserverwillreturna201CreatedstatuscodeandaJSONbodywiththerecentlyaddedmessageserializedtoJSON,includingtheassignedidthatwasautomaticallygeneratedbytheservertothemessageobject:

POSThttp://localhost:5000/api/messages/

WehavetocomposeandsendanHTTPrequestwiththeGETHTTPverbandthehttp://localhost:5000/api/messages/{id}requestURLtoretrievethemessagewhoseidmatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Forexample,ifweusetherequestURLhttp://localhost:5000/api/messages/82,theserverwillretrievethegamewhoseidmatches82.Asaresultoftherequest,theserverwillretrieveamessagewiththespecifiedidfromthedictionary.

Ifamessageisfound,theserverwillserializethemessageobjectintoJSONandreturna200OKstatuscodeandaJSONbodywiththeserializedmessageobject.Ifnomessagematchesthespecifiedidorprimarykey,theserverwillreturna404NotFoundstatus:

GEThttp://localhost:5000/api/messages/{id}

WehavetocomposeandsendanHTTPrequestwiththePATCHHTTPverbandthehttp://localhost:5000/api/messages/{id}requestURLtoupdateoneormorefieldsforthemessagewhoseidmatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Inaddition,wehavetoprovidetheJSONkey-valuepairswiththefieldnamestobeupdated

Page 213: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

andtheirnewvalues.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefields,updatethesefieldsonthemessagethatmatchesthespecifiedid,andupdatethemessageinthedictionary,ifitisavalidmessage.

Theserverwillreturna200OKstatuscodeandaJSONbodywiththerecentlyupdatedgameserializedtoJSON.Ifweprovideinvaliddataforthefieldstobeupdated,theserverwillreturna400BadRequeststatuscode.Iftheserverdoesn'tfindamessagewiththespecifiedid,theserverwillreturnjusta404NotFoundstatus:

PATCHhttp://localhost:5000/api/messages/{id}

Tip

ThePATCHmethodwillallowustoeasilyupdatetwofieldsforamessage:theintegercounter,thatindicatesthetimesthemessagehasbeenprintedandtheboolvalue,thatspecifieswhetherthemessagewasprintedatleastonce.

WehavetocomposeandsendanHTTPrequestwiththeDELETEHTTPverbandthehttp://localhost:5000/api/messages/{id}requestURLtoremovethemessagewhoseidmatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Forexample,ifweusetherequestURLhttp://localhost:5000/api/messages/15,theserverwilldeletethemessagewhoseidmatches15.Asaresultoftherequest,theserverwillretrieveamessagewiththespecifiedidfromthedictionary.Ifamessageisfound,theserverwillrequestthedictionarytodeletetheentryassociatedwiththismessageobjectandreturna204NoContentstatuscode.Ifnomessagematchesthespecifiedid,theserverwillreturna404NotFoundstatus:

DELETEhttp://localhost:5000/api/messages/{id}

Page 214: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupavirtualenvironmentwithFlaskandFlask-RESTfulInChapter1,DevelopingRESTfulAPIswithDjango,welearnedthat,throughoutthisbook,weweregoingtoworkwiththelightweightvirtualenvironmentsintroducedinPython3.4andimprovedinPython3.4.Now,wewillfollowthestepstocreateanewlightweightvirtualenvironmenttoworkwithFlaskandFlask-RESTful.ItishighlyrecommendedtoreadChapter1,DevelopingRESTfulAPIswithDjango,incaseyoudon'thaveexperiencewithlightweightvirtualenvironmentsinPython.Thechapterincludesallthedetailedexplanationsoftheeffectsofthestepswearegoingtofollow.

First,wehavetoselectthetargetfolderordirectoryforourvirtualenvironment.WewillusethefollowingpathintheexampleformacOSandLinux.ThetargetfolderforthevirtualenvironmentwillbethePythonREST/Flask01folderwithinourhomedirectory.Forexample,ifourhomedirectoryinmacOSorLinuxis/Users/gaston,thevirtualenvironmentwillbecreatedwithin/Users/gaston/PythonREST/Flask01.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand,asshown:

~/PythonREST/Flask01

WewillusethefollowingpathintheexampleforWindows.ThetargetfolderforthevirtualenvironmentwillbethePythonREST\Flask01folderwithinouruserprofilefolder.Forexample,ifouruserprofilefolderisC:\Users\Gaston,thevirtualenvironmentwillbecreatedwithinC:\Users\gaston\PythonREST\Flask01.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand,asshown:

%USERPROFILE%\PythonREST\Flask01

OpenaTerminalinmacOSorLinuxandexecutethefollowingcommandtocreateavirtualenvironment:

python3-mvenv~/PythonREST/Flask01

InWindows,executethefollowingcommandtocreateavirtualenvironment:

python-mvenv%USERPROFILE%\PythonREST\Flask01

Theprecedingcommanddoesn'tproduceanyoutput.Nowthatwehavecreatedavirtualenvironment,wewillrunaplatform-specificscripttoactivateit.Afterweactivatethevirtualenvironment,wewillinstallpackagesthatwillonlybeavailableinthisvirtualenvironment.

IfyourTerminalisconfiguredtousethebashshellinmacOSorLinux,runthefollowingcommandtoactivatethevirtualenvironment.Thecommandalsoworksforthezshshell:

source~/PythonREST/Flask01/bin/activate

Page 215: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

IfyourTerminalisconfiguredtouseeitherthecshortcshshell,runthefollowingcommandtoactivatethevirtualenvironment:

source~/PythonREST/Flask01/bin/activate.csh

IfyourTerminalisconfiguredtouseeitherthefishshell,runthefollowingcommandtoactivatethevirtualenvironment:

source~/PythonREST/Flask01/bin/activate.fish

InWindows,youcanruneitherabatchfileintheCommandPromptoraWindowsPowerShellscripttoactivatethevirtualenvironment.IfyouprefertheCommandPrompt,runthefollowingcommandintheWindowscommandlinetoactivatethevirtualenvironment:

%USERPROFILE%\PythonREST\Flask01\Scripts\activate.bat

IfyouprefertheWindowsPowerShell,launchitandrunthefollowingcommandstoactivatethevirtualenvironment.However,notethatyoushouldhavethescriptsexecutionenabledinWindowsPowerShelltobeabletorunthescript:

cd$env:USERPROFILE

PythonREST\Flask01\Scripts\Activate.ps1

Afteryouactivatethevirtualenvironment,theCommandPromptwilldisplaythevirtualenvironmentrootfoldername,enclosedinparenthesis,asaprefixforthedefaultprompt,toremindusthatweareworkinginthevirtualenvironment.Inthiscase,wewillsee(Flask01)asaprefixfortheCommandPromptbecausetherootfolderfortheactivatedvirtualenvironmentisFlask01.

Wehavecreatedandactivatedavirtualenvironment.NowitistimetorunthecommandsthatwillbethesameformacOS,Linux,orWindows;wemustrunthefollowingcommandtoinstallFlask-RESTfulwithpip.FlaskisadependencyforFlask-RESTful,andtherefore,pipwillinstallitautomatically,too:

pipinstallflask-restful

Thelastlinesfortheoutputwillindicateallthepackagesthathavebeensuccessfullyinstalled,includingflask-restfulandFlask:

Installingcollectedpackages:six,pytz,click,itsdangerous,MarkupSafe,

Jinja2,Werkzeug,Flask,python-dateutil,aniso8601,flask-restful

Runningsetup.pyinstallforclick

Runningsetup.pyinstallforitsdangerous

Runningsetup.pyinstallforMarkupSafe

Runningsetup.pyinstallforaniso8601

SuccessfullyinstalledFlask-0.11.1Jinja2-2.8MarkupSafe-0.23Werkzeug-

0.11.10aniso8601-1.1.0click-6.6flask-restful-0.3.5itsdangerous-0.24

python-dateutil-2.5.3pytz-2016.4six-1.10.0

Page 216: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DeclaringstatuscodesfortheresponsesNeitherFlasknorFlask-RESTfulincludesthedeclarationofvariablesforthedifferentHTTPstatuscodes.Wedon'twanttoreturnnumbersasstatuscodes.Wewantourcodetobeeasytoreadandunderstand,andtherefore,wewillusedescriptiveHTTPstatuscodes.WewillborrowthecodethatdeclaresusefulfunctionsandvariablesrelatedtoHTTPstatuscodesfromthestatus.pyfileincludedinDjangoRESTFramework,thatis,theframeworkwehavebeenusingintheprecedingchapters.

First,createafoldernamedapiwithintherootfolderfortherecentlycreatedvirtualenvironment,andthencreateanewstatus.pyfilewithintheapifolder.ThefollowinglinesshowthecodethatdeclaresfunctionsandvariableswithdescriptiveHTTPstatuscodesintheapi/models.pyfileborrowedfromtherest_framework.statusmodule.Wedon'twanttoreinventthewheel,andthemoduleprovideseverythingweneedtoworkwithHTTPstatuscodesinourFlask-basedAPI.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder:

defis_informational(code):

returncode>=100andcode<=199

defis_success(code):

returncode>=200andcode<=299

defis_redirect(code):

returncode>=300andcode<=399

defis_client_error(code):

returncode>=400andcode<=499

defis_server_error(code):

returncode>=500andcode<=599

HTTP_100_CONTINUE=100

HTTP_101_SWITCHING_PROTOCOLS=101

HTTP_200_OK=200

HTTP_201_CREATED=201

HTTP_202_ACCEPTED=202

HTTP_203_NON_AUTHORITATIVE_INFORMATION=203

HTTP_204_NO_CONTENT=204

HTTP_205_RESET_CONTENT=205

HTTP_206_PARTIAL_CONTENT=206

HTTP_300_MULTIPLE_CHOICES=300

HTTP_301_MOVED_PERMANENTLY=301

HTTP_302_FOUND=302

HTTP_303_SEE_OTHER=303

HTTP_304_NOT_MODIFIED=304

Page 217: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTP_305_USE_PROXY=305

HTTP_306_RESERVED=306

HTTP_307_TEMPORARY_REDIRECT=307

HTTP_400_BAD_REQUEST=400

HTTP_401_UNAUTHORIZED=401

HTTP_402_PAYMENT_REQUIRED=402

HTTP_403_FORBIDDEN=403

HTTP_404_NOT_FOUND=404

HTTP_405_METHOD_NOT_ALLOWED=405

HTTP_406_NOT_ACCEPTABLE=406

HTTP_407_PROXY_AUTHENTICATION_REQUIRED=407

HTTP_408_REQUEST_TIMEOUT=408

HTTP_409_CONFLICT=409

HTTP_410_GONE=410

HTTP_411_LENGTH_REQUIRED=411

HTTP_412_PRECONDITION_FAILED=412

HTTP_413_REQUEST_ENTITY_TOO_LARGE=413

HTTP_414_REQUEST_URI_TOO_LONG=414

HTTP_415_UNSUPPORTED_MEDIA_TYPE=415

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE=416

HTTP_417_EXPECTATION_FAILED=417

HTTP_428_PRECONDITION_REQUIRED=428

HTTP_429_TOO_MANY_REQUESTS=429

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE=431

HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS=451

HTTP_500_INTERNAL_SERVER_ERROR=500

HTTP_501_NOT_IMPLEMENTED=501

HTTP_502_BAD_GATEWAY=502

HTTP_503_SERVICE_UNAVAILABLE=503

HTTP_504_GATEWAY_TIMEOUT=504

HTTP_505_HTTP_VERSION_NOT_SUPPORTED=505

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED=511

ThecodedeclaresfivefunctionsthatreceivetheHTTPstatuscodeinthecodeargumentanddeterminewhichofthefollowingcategoriesthestatuscodebelongsto:informational,success,redirect,clienterror,orservererrorcategories.Wewillusethepreviousvariableswhenwehavetoreturnaspecificstatuscode.Forexample,incasewehavetoreturna404NotFoundstatuscode,wewillreturnstatus.HTTP_404_NOT_FOUND,insteadofjust404.

Page 218: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingthemodelNow,wewillcreateasimpleMessageModelclassthatwewillusetorepresentmessages.Rememberthatwewon'tbepersistingthemodelinthedatabase,andtherefore,inthiscase,ourclasswilljustprovidetherequiredattributesandnomappinginformation.Createanewmodels.pyfileintheapifolder.ThefollowinglinesshowthecodethatcreatesaMessageModelclassintheapi/models.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder:

classMessageModel:

def__init__(self,message,duration,creation_date,message_category):

#Wewillautomaticallygeneratethenewid

self.id=0

self.message=message

self.duration=duration

self.creation_date=creation_date

self.message_category=message_category

self.printed_times=0

self.printed_once=False

TheMessageModelclassjustdeclaresaconstructor,thatis,the__init__method.Thismethodreceivesmanyargumentsandthenusesthemtoinitializetheattributeswiththesamenames:message,duration,creation_date,andmessage_category.Theidattributeissetto0,printed_timesissetto0,andprinted_onceissettoFalse.WewillautomaticallyincrementtheidentifierforeachnewmessagegeneratedwithAPIcalls.

Page 219: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UsingadictionaryasarepositoryNow,wewillcreateaMessageManagerclassthatwewillusetopersisttheMessageModelinstancesinanin-memorydictionary.OurAPImethodswillcallmethodsfortheMessageManagerclasstoretrieve,insert,update,anddeleteMessageModelinstances.Createanewapi.pyfileintheapifolder.ThefollowinglinesshowthecodethatcreatesaMessageManagerclassintheapi/api.pyfile.Inaddition,thefollowinglinesdeclarealltheimportswewillneedforallthecodewewillwriteinthisfile.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder.

fromflaskimportFlask

fromflask_restfulimportabort,Api,fields,marshal_with,reqparse,Resource

fromdatetimeimportdatetime

frommodelsimportMessageModel

importstatus

frompytzimportutc

classMessageManager():

last_id=0

def__init__(self):

self.messages={}

definsert_message(self,message):

self.__class__.last_id+=1

message.id=self.__class__.last_id

self.messages[self.__class__.last_id]=message

defget_message(self,id):

returnself.messages[id]

defdelete_message(self,id):

delself.messages[id]

TheMessageManagerclassdeclaresalast_idclassattributeandinitializesitto0.ThisclassattributestoresthelastidthathasbeengeneratedandassignedtoaMessageModelinstancestoredinadictionary.Theconstructor,thatis,the__init__method,createsandinitializesthemessagesattributeasanemptydictionary.

Thecodedeclaresthefollowingthreemethodsfortheclass:

insert_message:ThismethodreceivesarecentlycreatedMessageModelinstanceinthemessageargument.Thecodeincreasesthevalueforthelast_idclassattributeandthenassignstheresultingvaluetotheidforthereceivedmessage.Thecodeusesself.__class__toreferencethetypeofthecurrentinstance.Finally,thecodeaddsthemessageasavaluetothekeyidentifiedwiththegeneratedid,last_id,intheself.messagesdictionary.get_message:Thismethodreceivestheidofthemessagethathastoberetrievedfromtheself.messagesdictionary.Thecodereturnsthevaluerelatedtothekeythatmatches

Page 220: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

thereceivedidintheself.messagesdictionarythatweareusingasourdatasource.delete_message:Thismethodreceivestheidofthemessagethathastoberemovedfromtheself.messagesdictionary.Thecodedeletesthekey-valuepairwhosekeymatchesthereceivedidintheself.messagesdictionarythatweareusingasourdatasource.

Wedon'tneedamethodtoupdateamessagebecausewewilljustmakechangestotheattributesoftheMessageModelinstancethatisalreadystoredintheself.messagesdictionary.ThevaluestoredinthedictionaryisareferencetotheMessageModelinstancethatweareupdating,andtherefore,wedon'tneedtocallaspecificmethodtoupdatetheinstanceinthedictionary.However,incasewewereworkingwithadatabase,wewouldneedtocallanupdatemethodforourORMordatarepository.

Page 221: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConfiguringoutputfieldsNow,wewillcreateamessage_fieldsdictionarythatwewillusetocontrolthedatathatwewantFlask-RESTfultorenderinourresponse,whenwereturnMessageModelinstances.Openthepreviouslycreatedapi/api.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder.

message_fields={

'id':fields.Integer,

'uri':fields.Url('message_endpoint'),

'message':fields.String,

'duration':fields.Integer,

'creation_date':fields.DateTime,

'message_category':fields.String,

'printed_times':fields.Integer,

'printed_once':fields.Boolean

}

message_manager=MessageManager()

Wedeclaredthemessage_fieldsdictionary(dict)withkey-valuepairsofstringsandclassesdeclaredintheflask_restful.fieldsmodule.ThekeysarethenamesoftheattributeswewanttorenderfromtheMessageModelclassandthevaluesaretheclassesthatformatandreturnthevalueforthefield.Inthepreviouscode,weworkedwiththefollowingclasses,thatformatandreturnthevalueforthespecifiedfieldinthekey:

field.Integer:Outputsanintegervalue.fields.Url:GeneratesastringrepresentationofaURL.Bydefault,thisclassgeneratesarelativeURIfortheresourcethatisbeingrequested.Thecodespecifies'message_endpoint'fortheendpointargument.Thisway,theclasswillusethespecifiedendpointname.Wewilldeclarethisendpointlaterintheapi.pyfile.Wedon'twanttoincludethehostnameinthegeneratedURI,andtherefore,weusethedefaultvaluefortheabsoluteboolattribute,whichisFalse.fields.DateTime:OutputsaformatteddatetimestringinUTC,inthedefaultRFC822format.fields.Boolean:Generatesastringrepresentationofaboolvalue.

The'uri'fieldusesfields.UrlanditisrelatedtothespecifiedendpointinsteadofbeingassociatedtoanattributeoftheMessageModelclass.Itistheonlycaseinwhichthespecifiedfieldnamedoesn'thaveanattributeintheMessageModelclass.Theotherstringsspecifiedaskeysindicatealltheattributeswewanttoberenderedintheoutputwhenweusethemessage_fieldsdictionarytomakeupthefinalserializedresponseoutput.

Afterwedeclaredthemessage_fieldsdictionary,thenextlineofcodecreatesaninstanceofthepreviouslycreatedMessageManagerclassnamedmessage_manager.Wewillusethisinstancetocreate,retrieve,anddeleteMessageModelinstances.

Page 222: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithresourcefulroutingontopofFlaskpluggableviewsFlask-RESTfulusesresourcesbuiltontopofFlaskpluggableviewsasthemainbuildingblockforaRESTfulAPI.Wejustneedtocreateasubclassoftheflask_restful.ResourceclassanddeclarethemethodsforeachsupportedHTTPverb.Asubclassofflask_restful.ResourcerepresentsaRESTfulresourceandtherefore,wewillhavetodeclareoneclasstorepresentthecollectionofmessagesandanotheronetorepresentthemessageresource.

First,wewillcreateaMessageclassthatwewillusetorepresentthemessageresource.Openthepreviouslycreatedapi/api.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder,asshown:

classMessage(Resource):

defabort_if_message_doesnt_exist(self,id):

ifidnotinmessage_manager.messages:

abort(

status.HTTP_404_NOT_FOUND,

message="Message{0}doesn'texist".format(id))

@marshal_with(message_fields)

defget(self,id):

self.abort_if_message_doesnt_exist(id)

returnmessage_manager.get_message(id)

defdelete(self,id):

self.abort_if_message_doesnt_exist(id)

message_manager.delete_message(id)

return'',status.HTTP_204_NO_CONTENT

@marshal_with(message_fields)

defpatch(self,id):

self.abort_if_message_doesnt_exist(id)

message=message_manager.get_message(id)

parser=reqparse.RequestParser()

parser.add_argument('message',type=str)

parser.add_argument('duration',type=int)

parser.add_argument('printed_times',type=int)

parser.add_argument('printed_once',type=bool)

args=parser.parse_args()

if'message'inargs:

message.message=args['message']

if'duration'inargs:

message.duration=args['duration']

if'printed_times'inargs:

message.printed_times=args['printed_times']

if'printed_once'inargs:

message.printed_once=args['printed_once']

returnmessage

Page 223: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TheMessageclassisasubclassofflask_restful.Resourceanddeclaresthefollowingthreemethods,thatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:Thismethodreceivestheidofthemessagethathastoberetrievedintheidargument.Thecodecallstheself.abort_if_message_doesnt_existmethodtoabortincasethereisnomessagewiththerequestedid.Incasethemessageexists,thecodereturnstheMessageModelinstancewhoseidthatmatchesthespecifiedidreturnedbythemessage_manager.get_messagemethod.Thegetmethodusesthe@marshal_withdecoratorwithmessage_fieldsasanargument.ThedecoratorwilltaketheMessageModelinstanceandapplythefieldfilteringandoutputformattingspecifiedinmessage_fields.delete:Thismethodreceivestheidofthemessagethathastobedeletedintheidargument.Thecodecallstheself.abort_if_message_doesnt_existmethodtoabort,incasethereisnomessagewiththerequestedid.Incasethe```messageexists,thecodecallsthemessage_manager.delete_messagemethodwiththereceivedidasanargumenttoremovetheMessageModelinstancefromourdatarepository.Then,thecodereturnsanemptyresponsebodyanda204NoContentstatuscode.patch:Thismethodreceivestheidofthemessagethathastobeupdatedorpatchedintheidargument.Thecodecallstheself.abort_if_message_doesnt_existmethodtoabortincasethereisnomessagewiththerequestedid.Incasethemessageexists,thecodesavestheMessageModelinstancewhoseidthatmatchesthespecifiedidreturnedbythemessage_manager.get_messagemethodinthemessagevariable.Thenextlinecreatesaflask_restful.reqparse.RequestParserinstancenamedparser.TheRequestParserinstanceallowsustoaddargumentswiththeirnamesandtypesandtheneasilyparsetheargumentsreceivedwiththerequest.Thecodemakesfourcallstotheparser.add_argumentwiththeargumentnameandthetypeofthefourargumentswewanttoparse.Then,thecodecallstheparser.parse_argsmethodtoparsealltheargumentsfromtherequestandsavesthereturneddictionary(dict)intheargsvariable.ThecodeupdatesalltheattributesthathavenewvaluesintheargsdictionaryintheMessageModelinstance:message.Incasetherequestdidn'tincludevaluesforcertainfields,thecodewon'tmakechangestotherealtedattributes.Therequestdoesn'trequiretoincludethefourfieldsthatcanbeupdatedwithvalues.Thecodereturnstheupdatedmessage.Thepatchmethodusesthe@marshal_withdecoratorwithmessage_fieldsasanargument.ThedecoratorwilltaketheMessageModelinstance,message,andapplythefieldfilteringandoutputformattingspecifiedinmessage_fields.

Tip

Weusedmultiplereturnvaluestosettheresponsecode.

Aspreviouslyexplained,thethreemethodscalltheinternalabort_if_message_doesnt_existmethodthatreceivestheidforanexistingMessageModelinstanceintheidargument.Ifthereceivedidisnotpresentinthekeysofthemessage_manager.messagesdictionary,themethodcallstheflask_restful.abortfunctionwithstatus.HTTP_404_NOT_FOUNDasthehttp_status_codeargumentandamessageindicatingthatthemessagewiththespecifiedid

Page 224: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

doesn'texists.TheabortfunctionraisesanHTTPExceptionforthereceivedhttp_status_codeandattachestheadditionalkeywordargumentstotheexceptionforlaterprocessing.Inthiscase,wegenerateanHTTP404NotFoundstatuscode.

Boththegetandpatchmethodsusethe@marshal_withdecoratorthattakesasingledataobjectoralistofdataobjectsandappliesthefieldfilteringandoutputformattingspecifiesasanargument.Themarshallingcanalsoworkwithdictionaries(dicts).Inbothmethods,wespecifiedmessage_fieldsasanargument,andtherefore,thecoderendersthefollowingfields:id,uri,message,duration,creation_date,message_category,printed_timesandprinted_once.Whenweusethe@marshal_withdecorator,weareautomaticallyreturninganHTTP200OKstatuscode.

Thefollowingreturnstatementwiththe@marshal_with(message_fields)decoratorreturnsanHTTP200OKstatuscodebecausewedidn'tspecifyanystatuscodeafterthereturnedobject(message):

returnmessage

Thenextlineisthelineofcodethatisreallyexecutedwiththe@marshal_with(message_fields)decorator,andwecanuseitinsteadofworkingwiththedecorator:

returnmarshal(message,resource_fields),status.HTTP_200_OK

Forexample,wecancallthemarshalfunctionasshowninthepreviouslineinsteadofusingthe@marshal_withdecoratorandthecodewillproducethesameresult.

Now,wewillcreateaMessageListclassthatwewillusetorepresentthecollectionofmessages.Openthepreviouslycreatedapi/api.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder:

classMessageList(Resource):

@marshal_with(message_fields)

defget(self):

return[vforvinmessage_manager.messages.values()]

@marshal_with(message_fields)

defpost(self):

parser=reqparse.RequestParser()

parser.add_argument('message',type=str,required=True,help='Message

cannotbeblank!')

parser.add_argument('duration',type=int,required=True,help='Duration

cannotbeblank!')

parser.add_argument('message_category',type=str,required=True,

help='Messagecategorycannotbeblank!')

args=parser.parse_args()

message=MessageModel(

message=args['message'],

duration=args['duration'],

creation_date=datetime.now(utc),

Page 225: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

message_category=args['message_category']

)

message_manager.insert_message(message)

returnmessage,status.HTTP_201_CREATED

TheMessageListclassisasubclassofflask_restful.ResourceanddeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:ThismethodreturnsalistwithalltheMessageModelinstancessavedinthemessage_manager.messagesdictionary.Thegetmethodusesthe@marshal_withdecoratorwithmessage_fieldsasanargument.ThedecoratorwilltakeeachMessageModelinstanceinthereturnedlistandapplythefieldfilteringandoutputformattingspecifiedinmessage_fields.post:Thismethodcreatesaflask_restful.reqparse.RequestParserinstancenamedparser.TheRequestParserinstanceallowsustoaddargumentswiththeirnamesandtypesandtheneasilyparsetheargumentsreceivedwiththePOSTrequesttocreateanewMessageModelinstance.Thecodemakesthreecallstotheparser.add_argumentwiththeargumentnameandthetypeofthethreeargumentswewanttoparse.Then,thecodecallstheparser.parse_argsmethodtoparsealltheargumentsfromtherequestandsavesthereturneddictionary(dict)intheargsvariable.Thecodeusestheparsedargumentsinthedictionarytospecifythevaluesforthemessage,durationandmessage_categoryattributestocreateanewMessageModelinstanceandsaveitinthemessagevariable.Thevalueforthecreation_dateargumentissettothecurrentdatetimewithtimezoneinfo,andtherefore,itisn'tparsedfromtherequest.Then,thecodecallsthemessage_manager.insert_messagemethodwiththenewMessageModelinstance(message)toaddthisnewinstancetothedictionary.Thepostmethodusesthe@marshal_withdecoratorwithmessage_fieldsasanargument.ThedecoratorwilltaketherecentlycreatedandstoredMessageModelinstance,message,andapplythefieldfilteringandoutputformattingspecifiedinmessage_fields.ThecodereturnsanHTTP201Createdstatuscode.

ThefollowingtableshowsthemethodofourpreviouslycreatedclassesthatwewanttobeexecutedforeachcombinationofHTTPverbandscope:

HTTPverb Scope Classandmethod

GET Collectionofmessages MessageList.get

GET Message Message.get

POST Collectionofmessages MessageList.post

Page 226: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

PATCH Message Message.patch

DELETE Message Message.delete

IftherequestresultsintheinvocationofaresourcewithanunsupportedHTTPmethod,Flask-RESTfulwillreturnaresponsewiththeHTTP405MethodNotAllowedstatuscode.

Page 227: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ConfiguringresourceroutingandendpointsWemustmakethenecessaryresourceroutingconfigurationstocalltheappropriatemethodsandpassthemallthenecessaryargumentsbydefiningURLrules.Thefollowinglinescreatethemainentrypointfortheapplication,initializeitwithaFlaskapplicationandconfiguretheresourceroutingfortheapi.Openthepreviouslycreatedapi/api.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_05_01folder:

app=Flask(__name__)

api=Api(app)

api.add_resource(MessageList,'/api/messages/')

api.add_resource(Message,'/api/messages/<int:id>',endpoint='message_endpoint')

if__name__=='__main__':

app.run(debug=True)

Thecodecreatesaninstanceoftheflask_restful.Apiclassandsavesitintheapivariable.Eachcalltotheapi.add_resourcemethodroutesaURLtoaresource,specificallytooneofthepreviouslydeclaredsubclassesoftheflask_restful.Resourceclass.WhenthereisarequesttotheAPIandtheURLmatchesoneoftheURLsspecifiedintheapi.add_resourcemethod,FlaskwillcallthemethodthatmatchestheHTTPverbintherequestforthespecifiedclass.ThemethodfollowsstandardFlaskroutingrules.

Forexample,thefollowinglinewillmakeanHTTPGETrequestto/api/messages/withoutanyadditionalparameterstocalltheMessageList.getmethod:

api.add_resource(MessageList,'/api/messages/')

FlaskwillpasstheURLvariablestothecalledmethodasarguments.Forexample,thefollowinglinewillmakeanHTTPGETrequestto/api/messages/12tocalltheMessage.getmethodwith12passedasthevaluefortheidargument:

api.add_resource(Message,'/api/messages/<int:id>',endpoint='message_endpoint')

Inaddition,wecanspecifyastringvaluefortheendpointargumenttomakeiteasytoreferencethespecifiedrouteinfields.Urlfields.Wepassthesameendpointname,'message_endpoint'asanargumentintheurifielddeclaredasfields.Urlinthemessage_fieldsdictionarythatweusetorendereachMessageModelinstance.Thisway,fields.UrlwillgenerateaURIconsideringthisroute.

Wejustrequiredafewlinesofcodetoconfigureresourceroutingandendpoints.Thelastlinejustcallstheapp.runmethodtostarttheFlaskapplicationwiththedebugargumentsettoTruetoenabledebugging.Inthiscase,westarttheapplicationbycallingtherunmethodtoimmediatelylaunchalocalserver.Wecouldalsoachievethesamegoalbyusingtheflaskcommand-linescript.However,thisoptionwouldrequireustoconfigureenvironment

Page 228: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

variablesandtheinstructionsaredifferentfortheplatformsthatwearecoveringinthisbook-macOS,WindowsandLinux.

Tip

AswithanyotherWebframework,youshouldneverenabledebugginginaproductionenvironment.

Page 229: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MakingHTTPrequeststotheFlaskAPINow,wecanruntheapi/api.pyscriptthatlaunchesFlask'sdevelopmentservertocomposeandsendHTTPrequeststoourunsecureandsimpleWebAPI(wewilldefinitelyaddsecuritylater).Executethefollowingcommand.

pythonapi/api.py

Thefollowinglinesshowtheoutputafterweexecutethepreviouscommand.Thedevelopmentserverislisteningatport5000.

*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)

*Restartingwithstat

*Debuggerisactive!

*Debuggerpincode:294-714-594

Withthepreviouscommand,wewillstartFlaskdevelopmentserverandwewillonlybeabletoaccessitinourdevelopmentcomputer.ThepreviouscommandstartsthedevelopmentserverinthedefaultIPaddress,thatis,127.0.0.1(localhost).ItisnotpossibletoaccessthisIPaddressfromothercomputersordevicesconnectedonourLAN.Thus,ifwewanttomakeHTTPrequeststoourAPIfromothercomputersordevicesconnectedtoourLAN,weshouldusethedevelopmentcomputerIPaddress,0.0.0.0(forIPv4configurations)or::(forIPv6configurations),asthedesiredIPaddressforourdevelopmentserver.

Ifwespecify0.0.0.0asthedesiredIPaddressforIPv4configurations,thedevelopmentserverwilllistenoneveryinterfaceonport5000.Inaddition,itisnecessarytoopenthedefaultport5000inourfirewalls(softwareand/orhardware)andconfigureport-forwardingtothecomputerthatisrunningthedevelopmentserver.

Wejustneedtospecify'0.0.0.0'asthevalueforthehostargumentinthecalltotheapp.runmethod,specifically,thelastlineintheapi/api.pyfile.Thefollowinglineshowsthenewcalltoapp.runthatlaunchesFlask'sdevelopmentserverinanIPv4configurationandallowsrequeststobemadefromothercomputersanddevicesconnectedtoourLAN.Thelinegeneratesanexternallyvisibleserver.Thecodefileforthesampleisincludedintherestful_python_chapter_05_02folder:

if__name__=='__main__':

app.run(host='0.0.0.0',debug=True)

Tip

IfyoudecidetocomposeandsendHTTPrequestsfromothercomputersordevicesconnectedtotheLAN,rememberthatyouhavetousethedevelopmentcomputer'sassignedIPaddressinsteadoflocalhost.Forexample,ifthecomputer'sassignedIPv4IPaddressis192.168.1.103,insteadoflocalhost:5000,youshoulduse192.168.1.103:5000.Ofcourse,youcanalsousethehostnameinsteadoftheIPaddress.Thepreviouslyexplainedconfigurationsareveryimportantbecausemobiledevicesmightbetheconsumersofour

Page 230: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

RESTfulAPIsandwewillalwayswanttotesttheappsthatmakeuseofourAPIsinourdevelopmentenvironments.Inaddition,wecanworkwithusefultoolssuchasngrokthatallowustogeneratesecuretunnelstolocalhost.Youcanreadmoreinformationaboutngrokathttp://www.ngrok.com.

TheFlaskdevelopmentserverisrunningonlocalhost(127.0.0.1),listeningonport5000,andwaitingforourHTTPrequests.Now,wewillcomposeandsendHTTPrequestslocallyinourdevelopmentcomputerorfromothercomputerordevicesconnectedtoourLAN.

Page 231: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Workingwithcommand-linetools–curlandhttpieWewillstartcomposingandsendingHTTPrequestswiththecommand-linetoolswehaveintroducedinChapter1,DevelopingRESTfulAPIswithDjango,curlandHTTPie.Incaseyouhaven'tinstalledHTTPie,makesureyouactivatethevirtualenvironmentandthenrunthefollowingcommandintheterminalorcommandprompttoinstalltheHTTPiepackage.

pipinstall--upgradehttpie

Tip

Incaseyoudon'trememberhowtoactivatethevirtualenvironmentthatwecreatedforthisexample,readthefollowingsectioninthischapter-SettingupthevirtualenvironmentwithDjangoRESTframework.

OpenaCygwinTerminalinWindowsoraTerminalinmacOSorLinux,andrunthefollowingcommand.Itisveryimportantthatyouentertheendingslash(/)whenspecified/api/messageswon'tmatchanyoftheconfiguredURLroutes.Thus,wemustenter/api/messages/,includingtheendingslash(/).WewillcomposeandsendanHTTPrequesttocreateanewmessage:

httpPOST:5000/api/messages/message='WelcometoIoT'duration=10

message_category='Information'

Thefollowingistheequivalentcurlcommand.Itisveryimportanttousethe-H"Content-Type:application/json"optiontoindicatecurltosendthedataspecifiedafterthe-doptionasapplication/jsoninsteadofthedefaultapplication/x-www-form-urlencoded:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Welcometo

IoT","duration":10,"message_category":"Information"}':5000/api/messages/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:POSThttp://localhost:5000/api/messages/withthefollowingJSONkey-valuepairs:

{

"message":"WelcometoIoT",

"duration":10,

"message_category":"Information"

}

Therequestspecifies/api/messages/,andtherefore,itwillmatch'/api/messages/'andruntheMessageList.postmethod.Themethoddoesn'treceiveargumentsbecausetheURLroutedoesn'tincludeanyparameters.AstheHTTPverbfortherequestisPOST,Flaskcallsthepostmethod.IfthenewMessageModelwassuccessfullypersistedinthedictionary,thefunctionreturnsanHTTP201CreatedstatuscodeandtherecentlypersistedMessageModelserializedserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewMessageModelobjectintheJSONresponse:

HTTP/1.0201CREATED

Page 232: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Content-Length:245

Content-Type:application/json

Date:Wed,20Jul201604:43:24GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"creation_date":"Wed,20Jul201604:43:24-0000",

"duration":10,

"id":1,

"message":"WelcometoIoT",

"message_category":"Information",

"printed_once":false,

"printed_times":0,

"uri":"/api/messages/1"

}

WewillcomposeandsendanHTTPrequesttocreateanothermessage.GobacktotheCygwinterminalinWindowsortheTerminalinmacOSorLinux,andrunthefollowingcommand:

httpPOST:5000/api/messages/message='Measuringambienttemperature'

duration=5message_category='Information'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Measuring

ambienttemperature","duration":5,"message_category":"Information"}'

:5000/api/messages/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest,POSThttp://localhost:5000/api/messages/,withthefollowingJSONkey-valuepairs:

{

"message":"Measuringambienttemperature",

"duration":5,

"message_category":"Information"

}

ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewMessageModelobjectintheJSONresponse:

HTTP/1.0201CREATED

Content-Length:259

Content-Type:application/json

Date:Wed,20Jul201618:27:05GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"creation_date":"Wed,20Jul201618:27:05-0000",

"duration":5,

"id":2,

"message":"Measuringambienttemperature",

"message_category":"Information",

"printed_once":false,

"printed_times":0,

"uri":"/api/messages/2"

Page 233: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

}

WewillcomposeandsendanHTTPrequesttoretrieveallthemessages.GobacktotheCygwinterminalinWindowsortheTerminalinmacOSorLinux,andrunthefollowingcommand:

http:5000/api/messages/

Thefollowingistheequivalentcurlcommand:

curl-iXGET-H:5000/api/messages/

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:5000/api/messages/.Therequestspecifies/api/messages/,andtherefore,itwillmatch'/api/messages/'andruntheMessageList.getmethod.Themethoddoesn'treceiveargumentsbecausetheURLroutedoesn'tincludeanyparameters.AstheHTTPverbfortherequestisGET,Flaskcallsthegetmethod.ThemethodretrievesalltheMessageModelobjectsandgeneratesaJSONresponsewithalloftheseMessageModelobjectsserialized.

ThefollowinglinesshowanexampleresponsefortheHTTPrequest.ThefirstlinesshowtheHTTPresponseheaders,includingthestatus(200OK)andtheContent-type(application/json).AftertheHTTPresponseheaders,wecanseethedetailsforthetwoMessageModelobjectsintheJSONresponse:

HTTP/1.0200OK

Content-Length:589

Content-Type:application/json

Date:Wed,20Jul201605:32:28GMT

Server:Werkzeug/0.11.10Python/3.5.1

[

{

"creation_date":"Wed,20Jul201605:32:06-0000",

"duration":10,

"id":1,

"message":"WelcometoIoT",

"message_category":"Information",

"printed_once":false,

"printed_times":0,

"uri":"/api/messages/1"

},

{

"creation_date":"Wed,20Jul201605:32:18-0000",

"duration":5,

"id":2,

"message":"Measuringambienttemperature",

"message_category":"Information",

"printed_once":false,

"printed_times":0,

"uri":"/api/messages/2"

}

]

Page 234: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Afterwerunthethreerequests,wewillseethefollowinglinesinthewindowthatisrunningtheFlaskdevelopmentserver.TheoutputindicatesthattheserverreceivedthreeHTTPrequests,specificallytwoPOSTrequestsandoneGETrequestwith/api/messages/astheURI.TheserverprocessedthethreeHTTPrequests,returnedstatuscode201forthefirsttworequestsand200forthelastrequest:

127.0.0.1--[20/Jul/201602:32:06]"POST/api/messages/HTTP/1.1"201-

127.0.0.1--[20/Jul/201602:32:18]"POST/api/messages/HTTP/1.1"201-

127.0.0.1--[20/Jul/201602:32:28]"GET/api/messages/HTTP/1.1"200-

ThefollowingimageshowstwoTerminalwindowsside-by-sideonmacOS.TheTerminalwindowattheleft-handsideisrunningtheFlaskdevelopmentserveranddisplaysthereceivedandprocessedHTTPrequests.TheTerminalwindowattheright-handsideisrunninghttpcommandstogeneratetheHTTPrequests.ItisagoodideatouseasimilarconfigurationtochecktheoutputwhilewecomposeandsendtheHTTPrequests:

Now,wewillcomposeandsendanHTTPrequesttoretrieveamessagethatdoesn'texist.Forexample,inthepreviouslist,thereisnomessagewithanidvalueequalto800.Runthefollowingcommandtotrytoretrievethismessage.Makesureyouuseanidvaluethatdoesn'texist.Wemustmakesurethattheutilitiesdisplaytheheadersaspartoftheresponsetoseethereturnedstatuscode:

Page 235: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

http:5000/api/messages/800

Thefollowingistheequivalentcurlcommand:

curl-iXGET:5000/api/messages/800

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:5000/api/messages/800.Therequestisthesamethanthepreviousonewehaveanalyzed,withadifferentnumberfortheidparameter.TheserverwillruntheMessage.getmethodwith800asthevaluefortheidargument.ThemethodwillexecutethecodethatretrievestheMessageModelobjectwhoseidmatchestheidvaluereceivedasanargument.However,thefirstlineintheMessageList.getmethodcallstheabort_if_message_doesnt_existmethodthatwon'tfindtheidinthedictionarykeysanditwillcalltheflask_restful.abortfunctionbecausethereisnomessagewiththespecifiedidvalue.Thus,thecodewillreturnanHTTP404NotFoundstatuscode.ThefollowinglinesshowanexampleheaderresponsefortheHTTPrequestandthemessageincludedinthebody.Inthiscase,wejustleavethedefaultmessage.Ofcourse,wecancustomizeitbasedonourspecificneeds:

HTTP/1.0404NOTFOUND

Content-Length:138

Content-Type:application/json

Date:Wed,20Jul201618:08:04GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"message":"Message800doesn'texist.YouhaverequestedthisURI

[/api/messages/800]butdidyoumean/api/messages/<int:id>?"

}

OurAPIisabletoupdateasinglefieldforanexistingresource,andtherefore,weprovideanimplementationforthePATCHmethod.Forexample,wecanusethePATCHmethodtoupdatetwofieldsforanexistingmessageandsetthevalueforitsprinted_oncefieldtotrueandprinted_timesto1.Wedon'twanttousethePUTmethodbecausethismethodismeanttoreplaceanentiremessage.ThePATCHmethodismeanttoapplyadeltatoanexistingmessage,andtherefore,itistheappropriatemethodtojustchangethevalueoftheprinted_onceandprinted_timesfields.

Now,wewillcomposeandsendanHTTPrequesttoupdateanexistingmessage,specifically,toupdatethevalueoftwofields.Makesureyoureplace2withtheidofanexistingmessageinyourconfiguration:

httpPATCH:5000/api/messages/2printed_once=trueprinted_times=1

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d'{"printed_once":"true",

"printed_times":1}':5000/api/messages/2

ThepreviouscommandwillcomposeandsendaPATCHHTTPrequestwiththespecified

Page 236: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

JSONkey-valuepairs.Therequesthasanumberafter/api/messages/,andtherefore,itwillmatch'/api/messages/<int:id>'andruntheMessage.patchmethod,thatis,thepatchmethodfortheMessageclass.IfaMessageModelinstancewiththespecifiedidexistsanditwassuccessfullyupdated,thecalltothemethodwillreturnanHTTP200OKstatuscodeandtherecentlyupdatedMessageModelinstanceserializedtoJSONintheresponsebody.Thefollowinglinesshowasampleresponse:

HTTP/1.0200OK

Content-Length:231

Content-Type:application/json

Date:Wed,20Jul201618:28:01GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"creation_date":"Wed,20Jul201618:27:05-0000",

"duration":0,

"id":2,

"message":"Measuringambienttemperature",

"message_category":"Information",

"printed_once":true,

"printed_times":1,

"uri":"/api/messages/2"

}

Tip

TheIoTdevicewillmakethepreviouslyexplainedHTTPrequestwhenitdisplaysthemessageforthefirsttime.Then,itwillmakeadditionalPATCHrequeststoupdatethevaluefortheprinted_timesfield.

Now,wewillcomposeandsendanHTTPrequesttodeleteanexistingmessage,specifically,thelastmessageweadded.AshappenedinourlastHTTPrequests,wehavetocheckthevalueassignedtoidinthepreviousresponseandreplace2inthecommandwiththereturnedvalue:

httpDELETE:5000/api/messages/2

Thefollowingistheequivalentcurlcommand:

curl-iXDELETE:5000/api/messages/2

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:DELETEhttp://localhost:5000/api/messages/2.Therequesthasanumberafter/api/messages/,andtherefore,itwillmatch'/api/messages/<int:id>'andruntheMessage.deletemethod,thatis,thedeletemethodfortheMessageclass.IfaMessageModelinstancewiththespecifiedidexistsanditwassuccessfullydeleted,thecalltothemethodwillreturnanHTTP204NoContentstatuscode.Thefollowinglinesshowasampleresponse:

HTTP/1.0204NOCONTENT

Content-Length:0

Content-Type:application/json

Date:Wed,20Jul201618:50:12GMT

Page 237: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Server:Werkzeug/0.11.10Python/3.5.1

Page 238: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithGUItools-PostmanandothersSofar,wehavebeenworkingwithtwoterminal-basedorcommand-linetoolstocomposeandsendHTTPrequeststoourFlaskdevelopmentserver-cURLandHTTPie.Now,wewillworkwithoneoftheGUItoolsweusedwhencomposingandsendingHTTPrequeststotheDjangodevelopmentserver-Postman.

Now,wewillusetheBuildertabinPostmantoeasilycomposeandsendHTTPrequeststolocalhost:5000andtesttheRESTfulAPIwiththisGUItool.RememberthatPostmandoesn'tsupportcurl-likeshorthandsforlocalhost,andtherefore,wecannotusethesameshorthandswehavebeenusingwhencomposingrequestswithcurlandHTTPie.

SelectGET inthedropdownmenuattheleft-handsideoftheEnterrequestURLtextbox,andenterlocalhost:5000/api/messages/inthistextboxattheright-handsideofthedropdown.Then,clickSendandPostmanwilldisplaytheStatus(200OK),thetimeittookfortherequesttobeprocessedandtheresponsebodywithallthegamesformattedasJSONwithsyntaxhighlighting(Prettyview).ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPGETrequest.

ClickonHeadersattheright-handsideofBodyandCookiestoreadtheresponseheaders.ThefollowingscreenshotshowsthelayoutfortheresponseheadersthatPostmandisplaysfor

Page 239: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

thepreviousresponse.NoticethatPostmandisplaystheStatusattheright-handsideoftheresponseanddoesn'tincludeitasthefirstlineoftheHeaders,ashappenedwhenweworkedwithboththecURLandHTTPieutilities:

Now,wewillusetheBuildertabinPostmantocomposeandsendanHTTPrequesttocreateanewmessage,specifically,aPOSTrequest.Followthenextsteps:

1. SelectPOST inthedrop-downmenuattheleft-handsideoftheEnterrequestURLtextbox,andenterlocalhost:5000/api/messages/inthistextboxattheright-handsideofthedropdown.

2. ClickBodyattheright-handsideofAuthorizationandHeaders,withinthepanelthatcomposestherequest.

3. ActivatetherawradiobuttonandselectJSON(application/json)inthedropdownattheright-handsideofthebinaryradiobutton.PostmanwillautomaticallyaddaContent-type=application/jsonheader,andtherefore,youwillnoticetheHeaderstabwillberenamedtoHeaders(1),indicatingusthatthereisonekey-valuepairspecifiedfortherequestheaders.

4. Enterthefollowinglinesinthetextboxbelowtheradiobuttons,withintheBodytab:

{

"message":"Measuringdistance",

"duration":5,

"message_category":"Information"

}

ThefollowingscreenshotshowstherequestbodyinPostman:

Page 240: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WefollowedthenecessarystepstocreateanHTTPPOSTrequestwithaJSONbodythatspecifiesthenecessarykey-valuepairstocreateanewgame.ClickSendandPostmanwilldisplaytheStatus(201Created),thetimeittookfortherequesttobeprocessedandtheresponsebodywiththerecentlyaddedgameformattedasJSONwithsyntaxhighlighting(Prettyview).ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPPOSTrequest:

Tip

IfwewanttocomposeandsendanHTTPPATCHrequestforourAPIwithPostman,itisnecessarytofollowthepreviouslyexplainedstepstoprovideJSONdatawithintherequestbody.

ClickortaponthevaluefortheurlfieldintheJSONresponsebody-/api/messages/2.You

Page 241: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

willnoticethatthevaluewillbeunderlinedwhenyouhoverthemousepointeroverit.PostmanwillautomaticallygenerateaGETrequesttolocalhost:5000/api/messages/2.ClickSendtorunitandretrievetherecentlyaddedmessage.ThefieldisusefultobrowsetheAPIwithatoolsuchasPostman.

BecausewemadethenecessarychangestogenerateanexternallyvisibleFlaskdevelopmentserver,wecanalsouseappsthatcancomposeandsendHTTPrequestsfrommobiledevicestoworkwiththeRESTfulAPI.Forexample,wecanworkwiththeiCurlHTTPApponiOSdevicessuchasiPadProandiPhone.InAndroiddevices,wecanworkwiththepreviouslyintroducedHTTPRequestApp.

ThefollowingscreenshotshowstheresultsofcomposingandsendingthefollowingHTTPrequestwiththeiCurlHTTPApp:GEThttp://192.168.2.3:5000/api/messages/.RememberthatyouhavetoperformthepreviouslyexplainedconfigurationsinyourLANandroutertobeabletoaccesstheFlaskdevelopmentserverfromotherdevicesconnectedtoyourLAN.Inthiscase,theIPassignedtothecomputerrunningtheFlaskWebserveris192.168.2.3,andtherefore,youmustreplacethisIPwiththeIPassignedtoyourdevelopmentcomputer.

Page 242: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Flask-RESTfuluseswhichofthefollowingasthemainbuildingblockforaRESTful

API?1. ResourcesbuiltontopofFlaskpluggableviews2. StatusesbuiltontopofFlaskresourceviews.3. ResourcesbuiltontopofFlaskpluggablecontrollers.

2. InordertobeabletoprocessanHTTPPOSTrequestonaresource,wemustdeclareamethodwiththefollowingnameinasubclassofflask_restful.Resource.1. post_restful2. post_method3. post

3. InordertobeabletoprocessanHTTPGETrequestonaresource,wemustdeclareamethodwiththefollowingnameinasubclassofflask_restful.Resource.1. get_restful2. get_method3. get

4. Asubclassofflask_restful.Resourcerepresents:1. Acontrollerresource.2. ARESTfulresource.3. AsingleRESTfulHTTPverb.

5. Ifweusethe@marshal_withdecoratorwithmessage_fieldsasanargument,thedecoratorwill:1. Applythefieldfilteringandoutputformattingspecifiedinmessage_fieldstothe

appropriateinstance.2. Applythefieldfilteringspecifiedinmessage_fieldstotheappropriateinstance,

withoutconsideringoutputformatting.3. Applytheoutputformattingspecifiedinmessage_fieldstotheappropriateinstance,

withoutconsideringfieldfiltering.

Page 243: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wedesignedaRESTfulAPItointeractwithasimpledictionarythatactedasadatarepositoryandperformCRUDoperationswithmessages.WedefinedtherequirementsforourAPIandweunderstoodthetasksperformedbyeachHTTPmethod.WesetupavirtualenvironmentwithFlaskandFlask-RESTful.

Wecreatedamodeltorepresentandpersistmessages.WelearnedtoconfigureserializationofmessagesintoJSONrepresentationswiththefeaturesincludedinFlask-RESTful.WewroteclassesthatrepresentresourcesandprocessthedifferentHTTPrequestsandweconfiguredtheURLpatternstorouteURLstoclasses.

Finally,westartedFlaskdevelopmentserverandweusedcommand-linetoolstocomposeandsendHTTPrequeststoourRESTfulAPIandanalyzedhoweachHTTPrequestwasprocessedinourcode.WealsoworkedwithGUItoolstocomposeandsendHTTPrequests.

NowthatweunderstandthebasicsofthecombinationofFlaskandFlask-RESTfultocreateRESTfulAPIs,wewillexpandthecapabilitiesoftheRESTfulWebAPIbytakingadvantageofadvancedfeaturesincludedinFlask-RESTfulandrelatedORMs,whichiswhatwearegoingtodiscussinthenextchapter.

Page 244: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter6.WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlaskInthischapter,wewillexpandthecapabilitiesoftheRESTfulAPIthatwestartedinthepreviouschapter.WewilluseSQLAlchemyasourORMtoworkwithaPostgreSQLdatabaseandwewilltakeadvantageofadvancedfeaturesincludedinFlaskandFlask-RESTfulthatwillallowustoeasilyorganizecodeforcomplexAPIs,suchasmodelsandblueprints.Inthischapter,wewill:

DesignaRESTfulAPItointeractwithaPostgreSQLdatabaseUnderstandthetasksperformedbyeachHTTPmethodInstallpackagestosimplifyourcommontasksCreateandconfigurethedatabaseWritecodeforthemodelswiththeirrelationshipsUseschemastovalidate,serialize,anddeserializemodelsCombineblueprintswithresourcefulroutingRegistertheblueprintandrunmigrationsCreateandretrieverelatedresources

Page 245: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DesigningaRESTfulAPItointeractwithaPostgreSQLdatabaseSofar,ourRESTfulAPIhasperformedCRUDoperationsonasimpledictionarythatactedasadatarepository.Now,wewanttocreateamorecomplexRESTfulAPIwithFlaskRESTfultointeractwithadatabasemodelthathastoallowustoworkwithmessagesthataregroupedintomessagecategories.InourpreviousRESTfulAPI,weusedastringattributetospecifythemessagecategoryforamessage.Inthiscase,wewanttobeabletoeasilyretrieveallthemessagesthatbelongtoaspecificmessagecategory,andtherefore,wewillhavearelationshipbetweenamessageandamessagecategory.

WemustbeabletoperformCRUDoperationsondifferentrelatedresourcesandresourcecollections.Thefollowinglistenumeratestheresourcesandtheclassnamethatwewillcreatetorepresentthemodel:

Messagecategories(Categorymodel)Messages(Messagemodel)

Themessagecategory(Category)justrequiresanintegername,andweneedthefollowingdataforamessage(Message):

AnintegeridentifierAforeignkeytoamessagecategory(Category)AstringmessageThedurationinsecondsthatwillindicatethetimethemessagehastobeprintedontheOLEDdisplayThecreationdateandtime.ThetimestampwillbeaddedautomaticallywhenaddinganewmessagetothecollectionAnintegercounterthatindicatesthetimesthemessagehasbeenprintedintheOLEDdisplayAboolvalueindicatingwhetherthemessagewasprintedatleastonceontheOLEDdisplay

Tip

WewilltakeadvantageofthemanypackagesrelatedtoFlaskRESTfulandSQLAlchemythatmakeiteasiertoserializeanddeserializedata,performvalidations,andintegrateSQLAlchemywithFlaskandFlaskRESTful.

Page 246: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthetasksperformedbyeachHTTPmethodThefollowingtableshowstheHTTPverbs,thescope,andthesemanticsforthemethodsthatournewAPImustsupport.EachmethodiscomposedofanHTTPverb,ascope,andallthemethodshavewell-definedmeaningsforalltheresourcesandcollections:

HTTPverb Scope Semantics

GET

Collectionofmessagecategories

Retrieveallthestoredmessagecategoriesinthecollectionandreturnthemsortedbytheirnameinascendingorder.EachcategorymustincludethefullURLfortheresource.Eachcategorymustincludealistwithallthedetailsforthemessagesthatbelongtothecategory.Themessagesdon'thavetoincludethecategoryinordertoavoidrepeatingdata.

GETMessagecategory

Retrieveasinglemessagecategory.Thecategorymustincludethesameinformationexplainedforeachcategorywhenweretrieveacollectionofmessagecategory.

POST

Collectionofmessagecategories

Createanewmessagecategoryinthecollection.

PATCHMessagecategory Updatethenameofanexistingmessagecategory.

DELETEMessagecategory Deleteanexistingmessagecategory.

GET

Collectionofmessages

Retrieveallthestoredmessagesinthecollection,sortedbytheirmessageinascendingorder.Eachmessagemustincludeitsmessagecategorydetails,includingthefullURLtoaccesstherelatedresource.Themessagecategorydetailsdon'thavetoincludethemessagesthatbelongtothecategory.ThemessagemustincludethefullURLtoaccesstheresource.

Page 247: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

GET MessageRetrieveasinglemessage.Themessagemustincludethesameinformationexplainedforeachmessagewhenweretrieveacollectionofmessages.

POST

Collectionofmessages

Createanewmessageinthecollection.

PATCH Message Updateanyofthefollowingfieldsofanexistingmessage:message,duration,printed_times,andprinted_once.

DELETE Message Deleteanexistingmessage.

Inaddition,ourRESTfulAPImustsupporttheOPTIONSmethodforalltheresourcesandcollectionofresources.WewilluseSQLAlchemyasourORMandwewillworkwithaPostgreSQLdatabase.However,incaseyoudon'twanttospendtimeinstallingPostgreSQL,youcanuseanyotherdatabasesupportedbySQLAlchemy,suchasMySQL.Incaseyouwantthesimplestdatabase,youcanworkwithSQLite.

Intheprecedingtable,therearemanymethodsandscopes.ThefollowinglistenumeratestheURIsforeachscopementionedintheprecedingtable,where{id}hastobereplacedwiththenumericidorprimarykeyoftheresource.Ashappenedinthepreviousexample,wewantourAPItodifferentiatecollectionsfromasingleresourceofthecollectionintheURLs.Whenwerefertoacollection,wewilluseaslash(/)asthelastcharacterfortheURLandwhenwerefertoasingleresourceofthecollection,wewon'tuseaslash(/)asthelastcharacterfortheURL:

Collectionofmessagecategories:/categories/Messagecategory:/category/{id}Collectionofmessages:/messages/Message:/message/{id}

Let'sconsiderthathttp://localhost:5000/api/istheURLfortheAPIrunningontheFlaskdevelopmentserver.WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:5000/api/categories/)toretrieveallthestoredmessagecategoriesinthecollection.Eachcategorywillincludealistwithallthemessagesthatbelongtothecategory.

GEThttp://localhost:5000/api/categories/

Page 248: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

InstallingpackagestosimplifyourcommontasksMakesureyouquitFlask'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheTerminalorCommandPromptwindowinwhichitisrunning.Now,wewillinstallmanyadditionalpackages.MakesureyouhaveactivatedthevirtualenvironmentwehavecreatedinthepreviouschapterandwenamedFlask01.Incaseyoucreatedanewvirtualenvironmenttoworkwiththisexampleoryoudownloadedthesamplecodeforthebook,makesureyouinstallthepackagesweusedinthepreviousexample.

Afteryouactivatethevirtualenvironment,itistimetoruncommandsthatwillbethesameforeithermacOS,Linux,orWindows.Wecaninstallallthenecessarypackageswithpipwithasinglecommand.However,wewillrunindependentcommandstomakeiteasiertodetectanyproblemsincaseaspecificinstallationfails.

Now,wemustrunthefollowingcommandtoinstallFlask-SQLAlchemywithpip.Flask-SQLAlchemyaddssupportfortheSQLAlchemyORMtoFlaskapplications.ThisextensionsimplifiesexecutingcommonSQLAlchemytaskswithinaFlaskapplication.SQLAlchemyisadependencyforFlask-SQLAlchemy,andtherefore,pipwillinstallitautomatically,too:

pipinstallFlask-SQLAlchemy

Thelastlinesoftheoutputwillindicateallthepackagesthathavebeensuccessfullyinstalled,includingSQLAlchemyandFlask-SQLAlchemy:

Installingcollectedpackages:SQLAlchemy,Flask-SQLAlchemy

Runningsetup.pyinstallforSQLAlchemy

Runningsetup.pyinstallforFlask-SQLAlchemy

SuccessfullyinstalledFlask-SQLAlchemy-2.1SQLAlchemy-1.0.14

RunthefollowingcommandtoinstallFlask-Migratewithpip.Flask-MigrateusestheAlembicpackagetohandleSQLAlchemydatabasemigrationsforFlaskapplications.WewilluseFlask-MigratetosetupourPostgreSQLdatabase.Flask-ScriptisoneofthedependenciesforFlask-Migrate,andtherefore,pipwillinstallitautomatically.Flask-ScriptaddssupportforwritingexternalscriptsinFlask,includingscriptstosetupadatabase.

pipinstallFlask-Migrate

Thelastlinesfortheoutputwillindicateallthepackagesthathavebeensuccessfullyinstalled,includingFlask-MigrateandFlask-Script.Theotherinstalledpackagesareadditionaldependencies:

Installingcollectedpackages:Mako,python-editor,alembic,Flask-Script,

Flask-Migrate

Runningsetup.pyinstallforMako

Runningsetup.pyinstallforpython-editor

Runningsetup.pyinstallforalembic

Page 249: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Runningsetup.pyinstallforFlask-Script

Runningsetup.pyinstallforFlask-Migrate

SuccessfullyinstalledFlask-Migrate-2.0.0Flask-Script-2.0.5Mako-1.0.4

alembic-0.8.7python-editor-1.0.1

Runthefollowingcommandtoinstallmarshmallowwithpip.MarshmallowisalightweightlibraryforconvertingcomplexdatatypestoandfromnativePythondatatypes.Marshmallowprovidesschemasthatwecanusetovalidateinputdata,deserializeinputdatatoapp-levelobjects,andserializeapp-levelobjectstoPythonprimitivetypes:

pipinstallmarshmallow

Thelastlinesfortheoutputwillindicatemarshmallowhasbeensuccessfullyinstalled:

Installingcollectedpackages:marshmallow

Successfullyinstalledmarshmallow-2.9.1

RunthefollowingcommandtoinstallMarshmallow-sqlalchemywithpip.Marshmallow-sqlalchemyprovidesSQLAlchemyintegrationwiththepreviouslyinstalledmarshmallowvalidation,serialization,anddeserializationlightweightlibrary:

pipinstallmarshmallow-sqlalchemy

Thelastlinesfortheoutputwillindicatemarshmallow-sqlalchemyhasbeensuccessfullyinstalled:

Installingcollectedpackages:marshmallow-sqlalchemy

Successfullyinstalledmarshmallow-sqlalchemy-0.10.0

Finally,runthefollowingcommandtoinstallFlask-Marshmallowwithpip.Flask-MarshmallowintegratesthepreviouslyinstalledmarshmallowlibrarywithFlaskapplicationsandmakesiteasytogenerateaURLandHyperlinkfields:

pipinstallFlask-Marshmallow

ThelastlinesfortheoutputwillindicateFlask-Marshmallowhasbeensuccessfullyinstalled:

Installingcollectedpackages:Flask-Marshmallow

SuccessfullyinstalledFlask-Marshmallow-0.7.0

Page 250: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingandconfiguringthedatabaseNow,wewillcreatethePostgreSQLdatabasethatwewilluseasarepositoryforourAPI.YouwillhavetodownloadandinstallaPostgreSQLdatabaseincaseyouaren'talreadyrunningitinyourcomputerorinadevelopmentserver.Youcandownloadandinstallthisdatabasemanagementsystemfromitswebpage:http://www.postgresql.org.IncaseyouareworkingwithmacOS,Postgres.appprovidesareallyeasywaytoinstallandusePostgreSQLonthisoperatingsystem:http://postgresapp.com:

Tip

YouhavetomakesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable.Youshouldbeabletoexecutethepsqlcommand-lineutilityfromyourcurrentTerminalorCommandPrompt.Incasethefolderisn'tincludedinthePATH,youwillreceiveanerrorindicatingthatthepg_configfilecannotbefoundwhentryingtoinstallthepsycopg2package.Inaddition,youwillhavetousethefullpathtoeachofthePostgreSQLcommand-linetoolswewilluseinthenextsteps.

WewillusethePostgreSQLcommand-linetoolstocreateanewdatabasenamedmessages.IncaseyoualreadyhaveaPostgreSQLdatabasewiththisname,makesurethatyouuseanothernameinallthecommandsandconfigurations.YoucanperformthesametaskwithanyPostgreSQLGUItool.IncaseyouaredevelopingonLinux,itisnecessarytorunthecommandsasthepostgresuser.RunthefollowingcommandinmacOSorWindowstocreateanewdatabasenamedmessages.Notethatthecommandwon'tproduceanyoutput:

createdbmessages

InLinux,runthefollowingcommandtousethepostgresuser:

sudo-upostgrescreatedbmessages

Now,wewillusethepsqlcommand-linetooltorunsomeSQLstatementstocreateaspecificuserthatwewilluseinFlaskandassignthenecessaryrolesforit.InmacOSorWindows,runthefollowingcommandtolaunchpsql:

psql

InLinux,runthefollowingcommandtousethepostgresuser:

sudo-upsql

Then,runthefollowingSQLstatementsandfinallyenter\qtoexitthepsqlcommand-linetool.Replaceuser_namewithyourdesiredusernametouseinthenewdatabaseandpasswordwithyourchosenpassword.WewillusetheusernameandpasswordintheFlaskconfiguration.Youdon'tneedtorunthestepsincaseyouarealreadyworkingwithaspecificuserinPostgreSQLandyouhavealreadygrantedprivilegestothedatabasefortheuser.Youwillseetheoutputindicatingthatthepermissionwasgranted.

Page 251: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CREATEROLEuser_nameWITHLOGINPASSWORD'password';

GRANTALLPRIVILEGESONDATABASEmessagesTOuser_name;

ALTERUSERuser_nameCREATEDB;

\q

ItisnecessarytoinstallthePsycopg2package(psycopg2).ThispackageisaPython-PostgreSQLDatabaseAdapterandSQLAlchemywilluseittointeractwithourrecentlycreatedPostgreSQLdatabase.

OncewemadesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable,wejustneedtorunthefollowingcommandtoinstallthispackage:

pipinstallpsycopg2

Thelastlinesoftheoutputwillindicatethatthepsycopg2packagehasbeensuccessfullyinstalled:

Collectingpsycopg2

Installingcollectedpackages:psycopg2

Runningsetup.pyinstallforpsycopg2

Successfullyinstalledpsycopg2-2.6.2

Incaseyouareusingthesamevirtualenvironmentthatwecreatedforthepreviousexample,theapifolderalreadyexists.Ifyoucreateanewvirtualenvironment,createafoldernamedapiwithintherootfolderforthecreatedvirtualenvironment.

Createanewconfig.pyfilewithintheapifolder.ThefollowinglinesshowthecodethatdeclaresvariablesthatdeterminetheconfigurationforFlaskandSQLAlchemy.TheSQL_ALCHEMY_DATABASE_URIvariablegeneratesanSQLAlchemyURIforthePostgreSQLdatabase.

MakesureyouspecifythedesireddatabasenameinthevalueforDB_NAMEandthatyouconfiguretheuser,password,host,andportbasedonyourPostgreSQLconfiguration.Incaseyoufollowedtheprevioussteps,usethesettingsspecifiedinthesesteps.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

importos

basedir=os.path.abspath(os.path.dirname(__file__))

DEBUG=True

PORT=5000

HOST="127.0.0.1"

SQLALCHEMY_ECHO=False

SQLALCHEMY_TRACK_MODIFICATIONS=True

SQLALCHEMY_DATABASE_URI="postgresql://{DB_USER}:

{DB_PASS}@{DB_ADDR}/{DB_NAME}".format(DB_USER="user_name",DB_PASS="password",

DB_ADDR="127.0.0.1",DB_NAME="messages")

SQLALCHEMY_MIGRATE_REPO=os.path.join(basedir,'db_repository')

Page 252: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WewillspecifythemodulecreatedearlierasanargumenttoafunctionthatwillcreateaFlaskapp.Thisway,wehaveonemodulethatspecifiesallthevaluesforthedifferentconfigurationvariablesandanothermodulethatcreatesaFlaskapp.WewillcreatetheFlaskappfactoryasourfinalsteptowardsournewAPI.

Page 253: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingmodelswiththeirrelationshipsNow,wewillcreatethemodelsthatwecanusetorepresentandpersistthemessagecategories,messages,andtheirrelationships.Opentheapi/models.pyfileandreplaceitscontentswiththefollowingcode.Thelinesthatdeclarefieldsrelatedtoothermodelsarehighlightedinthecodelisting.Incaseyoucreatedanewvirtualenvironment,createanewmodels.pyfilewithintheapifolder.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

frommarshmallowimportSchema,fields,pre_load

frommarshmallowimportvalidate

fromflask_sqlalchemyimportSQLAlchemy

fromflask_marshmallowimportMarshmallow

db=SQLAlchemy()

ma=Marshmallow()

classAddUpdateDelete():

defadd(self,resource):

db.session.add(resource)

returndb.session.commit()

defupdate(self):

returndb.session.commit()

defdelete(self,resource):

db.session.delete(resource)

returndb.session.commit()

classMessage(db.Model,AddUpdateDelete):

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

message=db.Column(db.String(250),unique=True,nullable=False)

duration=db.Column(db.Integer,nullable=False)

creation_date=db.Column(db.TIMESTAMP,

server_default=db.func.current_timestamp(),nullable=False)

category_id=db.Column(db.Integer,db.ForeignKey('category.id',

ondelete='CASCADE'),nullable=False)

category=db.relationship('Category',backref=db.backref('messages',

lazy='dynamic',order_by='Message.message'))

printed_times=db.Column(db.Integer,nullable=False,server_default='0')

printed_once=db.Column(db.Boolean,nullable=False,server_default='false')

def__init__(self,message,duration,category):

self.message=message

self.duration=duration

self.category=category

classCategory(db.Model,AddUpdateDelete):

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

Page 254: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

name=db.Column(db.String(150),unique=True,nullable=False)

def__init__(self,name):

self.name=name

First,thecodecreatesaninstanceoftheflask_sqlalchemy.SQLAlchemyclassnameddb.ThisinstancewillallowustocontroltheSQLAlchemyintegrationforourFlaskapplication.Inaddition,theinstancewillprovideaccesstoalltheSQLAlchemyfunctionsandclasses.

Then,thecodecreatesaninstanceoftheflask_marshmallow.Marshmallowclassnamedma.Itisveryimportanttocreatetheflask_sqlalchemy.SQLAlchemyinstancebeforetheMarshmallowinstance,andtherefore,ordermattersinthiscase.MarshmallowisawrapperclassthatintegratesMashmallowwithaFlaskapplication.TheinstancenamedmawillprovideaccesstotheSchemaclass,thefieldsdefinedinmarshmallow.fields,andtheFlask-specificfieldsdeclaredinflask_marshmallow.fields.Wewillusethemlaterwhenwedeclaretheschemasrelatedtoourmodels.

ThecodecreatestheAddUpdateDeleteclassthatdeclaresthefollowingthreemethodstoadd,update,anddeletearesourcethroughSQLAlchemysessions:

add:Thismethodreceivestheobjecttobeaddedintheresourceargumentandcallsthedb.session.addmethodwiththereceivedresourceasanargumenttocreatetheobjectintheunderlyingdatabase.Finally,thecodecommitsthesession.update:Thismethodjustcommitsthesessiontopersistthechangesmadetotheobjectsintheunderlyingdatabase.delete:Thismethodreceivestheobjecttobedeletedintheresourceargumentandcallsthedb.session.deletemethodwiththereceivedresourceasanargumenttoremovetheobjectintheunderlyingdatabase.Finally,thecodecommitsthesession.

Thecodedeclaresthefollowingtwomodels,specifically,twoclasses,asasubclassofboththedb.Model,andtheAddUpdateDeleteclasses:

Message

Category

Wespecifiedthefieldtypes,maximumlengths,anddefaultsformanyattributes.Theattributesthatrepresentfieldswithoutanyrelationshipareinstancesofthedb.Columnclass.BothmodelsdeclareanidattributeandspecifytheTruevaluefortheprimary_keyargumenttoindicateitistheprimarykey.SQLAlchemywillusethedatatogeneratethenecessarytablesinthePostgreSQLdatabase.

TheMessagemodeldeclaresthecategoryfieldwiththefollowingline:

category=db.relationship('Category',backref=db.backref('messages',

lazy='dynamic',order_by='Message.message'))

Thepreviouslineusesthedb.relationshipfunctiontoprovideamany-to-onerelationshiptotheCategorymodel.Thebackrefargumentspecifiesacalltothedb.backreffunctionwith

Page 255: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

'messages'asthefirstvaluethatindicatesthenametousefortherelationfromtherelatedCategoryobjectbacktoaMessageobject.Theorder_byargumentspecifies'Message.message'becausewewantthemessagesforeachcategorytobesortedbythevalueofthemessagefieldinascendingorder.

Bothmodelsdeclareaconstructor,thatis,the__init__method.ThisconstructorfortheMessagemodelreceivesmanyargumentsandusesthemtoinitializetheattributeswiththesamenames:message,duration,andcategory.TheconstructorfortheCategorymodelreceivesanameargumentandusesittoinitializetheattributewiththesamename.

Page 256: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Creatingschemastovalidate,serialize,anddeserializemodelsNow,wewillcreatetheFlask-Marshmallowschemasthatwewillusetovalidate,serialize,anddeserializethepreviouslydeclaredCategoryandMessagemodelsandtheirrelationships.Opentheapi/models.pyfileandaddthefollowingcodeaftertheexistinglines.Thelinesthatdeclarethefieldsrelatedtotheotherschemasarehighlightedinthecodelisting.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

classCategorySchema(ma.Schema):

id=fields.Integer(dump_only=True)

name=fields.String(required=True,validate=validate.Length(3))

url=ma.URLFor('api.categoryresource',id='<id>',_external=True)

messages=fields.Nested('MessageSchema',many=True,exclude=

('category',))

classMessageSchema(ma.Schema):

id=fields.Integer(dump_only=True)

message=fields.String(required=True,validate=validate.Length(1))

duration=fields.Integer()

creation_date=fields.DateTime()

category=fields.Nested(CategorySchema,only=['id','url','name'],

required=True)

printed_times=fields.Integer()

printed_once=fields.Boolean()

url=ma.URLFor('api.messageresource',id='<id>',_external=True)

@pre_load

defprocess_category(self,data):

category=data.get('category')

ifcategory:

ifisinstance(category,dict):

category_name=category.get('name')

else:

category_name=category

category_dict=dict(name=category_name)

else:

category_dict={}

data['category']=category_dict

returndata

Thecodedeclaresthefollowingtwoschemas,specifically,twosubclassesofthema.Schemaclass:

CategorySchema

MessageSchema

Wedon'tusetheFlask-Marshmallowfeaturesthatallowustoautomaticallydeterminetheappropriatetypeforeachattributebasedonthefieldsdeclaredinamodelbecausewewanttousespecificoptionsforeachfield.Wedeclaretheattributesthatrepresentfieldsasinstances

Page 257: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

oftheappropriateclassdeclaredinthemarshmallow.fieldsmodule.WheneverwespecifytheTruevalueforthedump_onlyargument,itmeansthatwewantthefieldtoberead-only.Forexample,wewon'tbeabletoprovideavaluefortheidfieldinanyoftheschemas.Thevalueforthisfieldwillbeautomaticallygeneratedbytheauto-incrementprimarykeyinthedatabase.

TheCategorySchemaclassdeclaresthenameattributeasaninstanceoffields.String.TherequiredargumentissettoTruetospecifythatthefieldcannotbeanemptystring.Thevalidateargumentissettovalidate.Length(3)tospecifythatthefieldmusthaveaminimumlengthof3characters.

Theclassdeclarestheurlfieldwiththefollowingline:

url=ma.URLFor('api.categoryresource',id='<id>',_external=True)

Theurlattributeisaninstanceofthema.URLForclass,andthisfieldwilloutputthefullURLoftheresource,thatis,ofthemessagecategoryresource.ThefirstargumentistheFlaskendpointname-'api.categoryresource'.WewillcreateaCategoryResourceclasslaterandtheURLForclasswilluseittogeneratetheURL.Theidargumentspecifies'<id>'becausewewanttheidtobepulledfromtheobjecttobeserialized.Theidstringenclosedwithinlessthan(<)andgreaterthan(>)symbolsspecifiesthatwewantthefieldtobepulledfromtheobjectthathastobeserialized.The_externalattributeissettoTruebecausewewanttogeneratethefullURLfortheresource.Thisway,eachtimeweserializeaCategory,itwillincludethefullURLfortheresourceintheurlkey.

Tip

Inthiscase,weareusingourinsecureAPIbehindHTTP.IncaseourAPIisconfiguredwithHTTPS,weshouldsetthe_schemeargumentto'https'whenwecreatethema.URLForinstance.

Theclassdeclaresthemessagesfieldwiththefollowingline:

messages=fields.Nested('MessageSchema',many=True,exclude=('category',)0029

Themessagesattributeisaninstanceofthemarshmallow.fields.Nestedclass,andthisfieldwillnestacollectionofSchema,andtherefore,wespecifyTrueforthemanyargument.ThefirstargumentspecifiesthenameforthenestedSchemaclassasastring.WedeclaretheMessageSchemaclassafterwedefinedtheCategorySchemaclass.Thus,wespecifytheSchemaclassnameasastringinsteadofusingthetypethatwehaven'tdefinedyet.

Infact,wewillendupwithtwoobjectsthatnesttoeachother,thatis,wewillcreateatwo-waynestingbetweencategoriesandmessages.Weusetheexcludeparameterwithatupleofstringtoindicatethatwewantthecategoryfieldtobeexcludedfromthefieldsthatareserializedforeachmessage.Thisway,wecanavoidinfiniterecursionbecausetheinclusionofthecategoryfieldwouldserializeallthemessagesrelatedtothecategory.

Page 258: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WhenwedeclaredtheMessagemodel,weusedthedb.relationshipfunctiontoprovideamany-to-onerelationshiptotheCategorymodel.Thebackrefargumentspecifiedacalltothedb.backreffunctionwith'messages'asthefirstvaluethatindicatesthenametousefortherelationfromtherelatedCategoryobjectbacktoaMessageobject.Withthepreviouslyexplainedline,wecreatedthemessagesfieldsthatusesthesamenameweindicatedforthedb.backreffunction.

TheMessageSchemaclassdeclaresthemessageattributeasaninstanceoffields.String.TherequiredargumentissettoTruetospecifythatthefieldcannotbeanemptystring.Thevalidateargumentissettovalidate.Length(1)tospecifythatthefieldmusthaveaminimumlengthof1character.Theclassdeclarestheduration,creation_date,printed_timesandprinted_oncefieldswiththecorrespondingclassesbasedonthetypesweusedintheMessagemodel.

Theclassdeclaresthecategoryfieldwiththefollowingline:

category=fields.Nested(CategorySchema,only=['id','url','name'],

required=True)

Thecategoryattributeisaninstanceofthemarshmallow.fields.NestedclassandthisfieldwillnestasingleCategorySchema.WespecifyTruefortherequiredargumentbecauseamessagemustbelongtoacategory.ThefirstargumentspecifiesthenameforthenestedSchemaclass.WealreadydeclaredtheCategorySchemaclass,andtherefore,wespecifyCategorySchemaasthevalueforthefirstargument.WeusetheonlyparameterwithalistofstringtoindicatethefieldnamesthatwewanttobeincludedwhenthenestedCategorySchemaisserialized.Wewanttheid,url,andnamefieldstobeincluded.Wedon'tspecifythemessagesfieldbecausewedon'twantthecategorytoserializethelistofmessagesthatbelongtoit.

Theclassdeclarestheurlfieldwiththefollowingline:

url=ma.URLFor('api.messageresource',id='<id>',_external=True)

Theurlattributeisaninstanceofthema.URLForclassandthisfieldwilloutputthefullURLoftheresource,thatis,ofthemessageresource.ThefirstargumentistheFlaskendpointname:'api.messageresource'.WewillcreateaMessageResourceclasslaterandtheURLForclasswilluseittogeneratetheURL.Theidargumentspecifies'<id>'becausewewanttheidtobepulledfromtheobjecttobeserialized.The_externalattributeissettoTruebecausewewanttogeneratethefullURLfortheresource.Thisway,eachtimeweserializeaMessage,itwillincludethefullURLfortheresourceintheurlkey.

TheMessageSchemaclassdeclaresaprocess_categorymethodthatusesthe@pre_loaddecorator,specifically,marshmallow.pre_load.Thisdecoratorregistersamethodtoinvokebeforedeserializinganobject.Thisway,beforeMarshmallowdeserializesamessage,theprocess_categorymethodwillbeexecuted.

Page 259: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Themethodreceivesthedatatobedeserializedinthedataargumentanditreturnstheprocesseddata.WhenwereceivearequesttoPOSTanewmessage,thecategorynamecanbespecifiedinakeynamed'category'.Ifacategorywiththespecifiednameexists,wewillusetheexistingcategoryastheonethatisrelatedtothenewmessage.Ifacategorywiththespecifiednamedoesn'texist,wewillcreateanewcategoryandthenwewillusethisnewcategoryastheonethatisrelatedtothenewmessage.Thisway,wemakeiteasyfortheusertocreatenewmessages.

Thedataargumentmighthaveacategorynamespecifiedasastringforthe'category'key.However,inothercases,the'category'keywillincludethekey-valuepairswiththefieldnameandfieldvaluesforanexistingcategory.Thecodeintheprocess_categorymethodchecksthevalueforthe'category'keyandreturnsadictionarywiththeappropriatedatatomakeitsurethatweareabletodeserializeacategorywiththeappropriatekey-valuepairs,nomatterthedifferencesoftheincomingdata.Finally,themethodsreturnedtheprocesseddictionary.Wewilldivedeepontheworkdonebytheprocess_categorymethodlaterwhenwestartcomposingandsendingHTTPrequeststotheAPI.

Page 260: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CombiningblueprintswithresourcefulroutingNow,wewillcreatetheresourcesthatcomposeourmainbuildingblocksfortheRESTfulAPI.First,wewillcreateafewinstancesthatwewilluseinthedifferentresources.Then,wewillcreateaMessageResourceclass,thatwewillusetorepresentthemessageresource.Createanewviews.pyfilewithintheapifolderandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder,asshown:

fromflaskimportBlueprint,request,jsonify,make_response

fromflask_restfulimportApi,Resource

frommodelsimportdb,Category,CategorySchema,Message,MessageSchema

fromsqlalchemy.excimportSQLAlchemyError

importstatus

api_bp=Blueprint('api',__name__)

category_schema=CategorySchema()

message_schema=MessageSchema()

api=Api(api_bp)

classMessageResource(Resource):

defget(self,id):

message=Message.query.get_or_404(id)

result=message_schema.dump(message).data

returnresult

defpatch(self,id):

message=Message.query.get_or_404(id)

message_dict=request.get_json(force=True)

if'message'inmessage_dict:

message.message=message_dict['message']

if'duration'inmessage_dict:

message.duration=message_dict['duration']

if'printed_times'inmessage_dict:

message.printed_times=message_dict['printed_times']

if'printed_once'inmessage_dict:

message.printed_once=message_dict['printed_once']

dumped_message,dump_errors=message_schema.dump(message)

ifdump_errors:

returndump_errors,status.HTTP_400_BAD_REQUEST

validate_errors=message_schema.validate(dumped_message)

#errors=message_schema.validate(data)

ifvalidate_errors:

returnvalidate_errors,status.HTTP_400_BAD_REQUEST

try:

message.update()

returnself.get(id)

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_400_BAD_REQUEST

defdelete(self,id):

Page 261: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

message=Message.query.get_or_404(id)

try:

delete=message.delete(message)

response=make_response()

returnresponse,status.HTTP_204_NO_CONTENT

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_401_UNAUTHORIZED

Thefirstlinesdeclaretheimportsandcreatethefollowinginstancesthatwewilluseinthedifferentclasses:

api_bp:Itisaninstanceoftheflask.BlueprintclassthatwillallowustofactortheFlaskapplicationintothisblueprint.ThefirstargumentspecifiestheURLprefixonwhichwewanttoregistertheblueprint:'api'.category_schema:ItisaninstanceoftheCategorySchemaclasswedeclaredinthemodels.pymodule.Wewillusecategory_schematovalidate,serialize,anddeserializecategories.message_schema:ItisaninstanceoftheMessageSchemaclasswedeclaredinthemodels.pymodule.Wewillusemessage_schematovalidate,serializeand,deserializecategories.api:Itisaninstanceoftheflask_restful.Apiclassthatrepresentsthemainentrypointfortheapplication.Wepassthepreviouslycreatedflask.Blueprintinstancenamedapi_bpasanargumenttolinktheApitotheBlueprint.

TheMessageResourceclassisasubclassofflask_restful.ResourceanddeclaresthefollowingthreemethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:Thismethodreceivestheidofthemessagethathastoberetrievedintheidargument.ThecodecallstheMessage.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnomessagewiththerequestedidintheunderlyingdatabase.Incasethemessageexists,thecodecallsthemessage_schema.dumpmethodwiththeretrievedmessageasanargumenttousetheMessageSchemainstancetoserializetheMessageinstancewhoseidmatchesthespecifiedid.ThedumpmethodtakestheMessageinstanceandappliesthefieldfilteringandoutputformattingspecifiedintheMessageSchemaclass.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessageinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.delete:Thismethodreceivestheidofthemessagethathastobedeletedintheidargument.ThecodecallstheMessage.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnomessagewiththerequestedidintheunderlyingdatabase.Incasethemessageexists,thecodecallsthemessage.deletemethodwiththeretrievedmessageasanargumenttousetheMessageinstancetoeraseitselffromthedatabase.Then,thecodereturnsanemptyresponsebodyanda204NoContentstatuscode.patch:Thismethodreceivestheidofthemessagethathastobeupdatedorpatchedinthe

Page 262: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

idargument.ThecodecallstheMessage.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnomessagewiththerequestedidintheunderlyingdatabase.Incasethemessageexists,thecodecallstherequest.get_jsonmethodtoretrievethekey-valuepairsreceivedasargumentswiththerequest.Thecodeupdatesspecificattributesincasetheyhavenewvaluesinthemessage_dictdictionaryintheMessageinstance:message.Then,thecodecallsthemessage_schema.dumpmethodtoretrieveanyerrorsgeneratedwhenserializingtheupdatedmessage.Incasetherewereerrors,thecodereturnstheerrorsandanHTTP400BadRequeststatus.Iftheserializationdidn'tgenerateerrors,thecodecallsthemessage_schema.validatemethodtoretrieveanyerrorsgeneratedwhilevalidatingtheupdatedmessage.Incasetherewerevalidationerrors,thecodereturnsthevalidationerrorsandanHTTP400BadRequeststatus.Ifthevalidationissuccessful,thecodecallstheupdatemethodfortheMessageinstancetopersistthechangesinthedatabaseandreturnstheresultsofcallingthepreviouslyexplainedself.getmethodwiththeidoftheupdatedmessageasanargument.Thisway,themethodreturnstheserializedupdatedmessageinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.

Now,wewillcreateaMessageListResourceclassthatwewillusetorepresentthecollectionofmessages.Openthepreviouslycreatedapi/views.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

classMessageListResource(Resource):

defget(self):

messages=Message.query.all()

result=message_schema.dump(messages,many=True).data

returnresult

defpost(self):

request_dict=request.get_json()

ifnotrequest_dict:

response={'message':'Noinputdataprovided'}

returnresponse,status.HTTP_400_BAD_REQUEST

errors=message_schema.validate(request_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

try:

category_name=request_dict['category']['name']

category=Category.query.filter_by(name=category_name).first()

ifcategoryisNone:

#CreateanewCategory

category=Category(name=category_name)

db.session.add(category)

#Nowthatwearesurewehaveacategory

#createanewMessage

message=Message(

message=request_dict['message'],

duration=request_dict['duration'],

category=category)

message.add(message)

query=Message.query.get(message.id)

result=message_schema.dump(query).data

Page 263: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

returnresult,status.HTTP_201_CREATED

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_400_BAD_REQUEST

TheMessageListResourceclassisasubclassofflask_restful.ResourceanddeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:ThismethodreturnsalistwithalltheMessageinstancessavedinthedatabase.First,thecodecallstheMessage.query.allmethodtoretrievealltheMessageinstancespersistedinthedatabase.Then,thecodecallsthemessage_schema.dumpmethodwiththeretrievedmessagesandthemanyargumentsettoTruetoserializetheiterablecollectionofobjects.ThedumpmethodwilltakeeachMessageinstanceretrievedfromthedatabaseandapplythefieldfilteringandoutputformattingspecifiedtheMessageSchemaclass.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessagesinJSONformatasthebodywiththedefaultHTTP200OKstatuscode.post:Thismethodretrievesthekey-valuepairsreceivedintheJSONbody,createsanewMessageinstanceandpersistsitinthedatabase.Incasethespecifiedcategorynameexists,itusestheexistingcategory.Otherwise,themethodcreatesanewCategoryinstanceandassociatesthenewmessagetothisnewcategory.First,thecodecallstherequest.get_jsonmethodtoretrievethekey-valuepairsreceivedasargumentswiththerequest.Then,thecodecallsthemessage_schema.validatemethodtovalidatethenewmessagebuiltwiththeretrievedkey-valuepairs.RememberthattheMessageSchemaclasswillexecutethepreviouslyexplainedprocess_categorymethodbeforewecallthevalidatemethod,andtherefore,thedatawillbeprocessedbeforethevalidationtakesplace.Incasetherewerevalidationerrors,thecodereturnsthevalidationerrorsandanHTTP400BadRequeststatus.Ifthevalidationissuccessful,thecoderetrievesthecategorynamereceivedintheJSONbody,specificallyinthevalueforthe'name'keyofthe'category'key.Then,thecodecallstheCategory.query.filter_bymethodtoretrieveacategorythatmatchestheretrievedcategoryname.Ifnomatchisfound,thecodecreatesanewCategorywiththeretrievednameandpersistsinthedatabase.Then,thecodecreatesanewmessagewiththemessage,duration,andtheappropriateCategoryinstance,andpersistsitinthedatabase.Finally,thecodereturnstheserializedsavedmessageinJSONformatasthebody,withtheHTTP201Createdstatuscode.

Now,wewillcreateaCategoryResourceclassthatwewillusetorepresentacategoryresource.Openthepreviouslycreatedapi/views.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

classCategoryResource(Resource):

defget(self,id):

category=Category.query.get_or_404(id)

result=category_schema.dump(category).data

returnresult

Page 264: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

defpatch(self,id):

category=Category.query.get_or_404(id)

category_dict=request.get_json()

ifnotcategory_dict:

resp={'message':'Noinputdataprovided'}

returnresp,status.HTTP_400_BAD_REQUEST

errors=category_schema.validate(category_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

try:

if'name'incategory_dict:

category.name=category_dict['name']

category.update()

returnself.get(id)

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_400_BAD_REQUEST

defdelete(self,id):

category=Category.query.get_or_404(id)

try:

category.delete(category)

response=make_response()

returnresponse,status.HTTP_204_NO_CONTENT

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_401_UNAUTHORIZED

TheCategoryResourceclassisasubclassofflask_restful.ResourceanddeclaresthefollowingthreemethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:Thismethodreceivestheidofthecategorythathastoberetrievedintheidargument.ThecodecallstheCategory.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnocategorywiththerequestedidintheunderlyingdatabase.Incasethemessageexists,thecodecallsthecategory_schema.dumpmethodwiththeretrievedcategoryasanargumenttousetheCategorySchemainstancetoserializetheCategoryinstancewhoseidmatchesthespecifiedid.ThedumpmethodtakestheCategoryinstanceandappliesthefieldfilteringandoutputformattingspecifiedintheCategorySchemaclass.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessageinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.patch:Thismethodreceivestheidofthecategorythathastobeupdatedorpatchedintheidargument.ThecodecallstheCategory.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnocategorywiththerequestedidintheunderlyingdatabase.Incasethecategoryexists,thecodecallstherequest.get_jsonmethodtoretrievethekey-valuepairsreceivedasargumentswiththerequest.Thecodeupdatesjustthenameattributeincaseithasanewvalueinthecategory_dictdictionaryintheCategoryinstance:category.Then,thecodecallsthecategory_schema.validatemethod

Page 265: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

toretrieveanyerrorsgeneratedwhenvalidatingtheupdatedcategory.Incasetherewerevalidationerrors,thecodereturnsthevalidationerrorsandanHTTP400BadRequeststatus.Ifthevalidationissuccessful,thecodecallstheupdatemethodfortheCategoryinstancetopersistthechangesinthedatabaseandreturnstheresultsofcallingthepreviouslyexplainedself.getmethodwiththeidoftheupdatedcategoryasanargument.Thisway,themethodreturnstheserializedupdatedmessageinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.delete:Thismethodreceivestheidofthecategorythathastobedeletedintheidargument.ThecodecallstheCategory.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnocategorywiththerequestedidintheunderlyingdatabase.Incasethecategoryexists,thecodecallsthecategory.deletemethodwiththeretrievedcategoryasanargumenttousetheCategoryinstancetoeraseitselffromthedatabase.Then,thecodereturnsanemptyresponsebodyanda204NoContentstatuscode.

Now,wewillcreateaCategoryListResourceclassthatwewillusetorepresentthecollectionofcategories.Openthepreviouslycreatedapi/views.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

classCategoryListResource(Resource):

defget(self):

categories=Category.query.all()

results=category_schema.dump(categories,many=True).data

returnresults

defpost(self):

request_dict=request.get_json()

ifnotrequest_dict:

resp={'message':'Noinputdataprovided'}

returnresp,status.HTTP_400_BAD_REQUEST

errors=category_schema.validate(request_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

try:

category=Category(request_dict['name'])

category.add(category)

query=Category.query.get(category.id)

result=category_schema.dump(query).data

returnresult,status.HTTP_201_CREATED

exceptSQLAlchemyErrorase:

db.session.rollback()

resp=jsonify({"error":str(e)})

returnresp,status.HTTP_400_BAD_REQUEST

TheCategoryListResourceclassisasubclassofflask_restful.ResourceanddeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:ThismethodreturnsalistwithalltheCategoryinstancessavedinthedatabase.First,thecodecallstheCategory.query.allmethodtoretrievealltheCategoryinstances

Page 266: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

persistedinthedatabase.Then,thecodecallsthecategory_schema.dumpmethodwiththeretrievedmessagesandthemanyargumentsettoTruetoserializetheiterablecollectionofobjects.ThedumpmethodwilltakeeachCategoryinstanceretrievedfromthedatabaseandapplythefieldfilteringandoutputformattingspecifiedtheCategorySchemaclass.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessagesinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.post:Thismethodretrievesthekey-valuepairsreceivedintheJSONbody,createsanewCategoryinstanceandpersistsitinthedatabase.First,thecodecallstherequest.get_jsonmethodtoretrievethekey-valuepairsreceivedasargumentswiththerequest.Then,thecodecallsthecategory_schema.validatemethodtovalidatethenewcategorybuiltwiththeretrievedkey-valuepairs.Incasetherewerevalidationerrors,thecodereturnsthevalidationerrorsandanHTTP400BadRequeststatus.Ifthevalidationissuccessful,thecodecreatesanewcategorywiththespecifiedname,andpersistsitinthedatabase.Finally,thecodereturnstheserializedsavedcategoryinJSONformatasthebody,withtheHTTP201Createdstatuscode.

ThefollowingtableshowsthemethodofourpreviouslycreatedclassesthatwewanttobeexecutedforeachcombinationofHTTPverbandscope:

HTTPverb Scope Classandmethod

GET Collectionofmessages MessageListResource.get

GET Message MessageResource.get

POST Collectionofmessages MessageListResource.post

PATCH Message MessageResource.patch

DELETE Message MessageResource.delete

GET Collectionofcategories CategoryListResource.get

GET Message CategoryResource.get

POST Collectionofmessages CategoryListResource.post

PATCH Message CategoryResource.patch

Page 267: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DELETE Message CategoryResource.delete

IftherequestresultsintheinvocationofaresourcewithanunsupportedHTTPmethod,Flask-RESTfulwillreturnaresponsewiththeHTTP405MethodNotAllowedstatuscode.

WemustmakethenecessaryresourceroutingconfigurationstocalltheappropriatemethodsandpassthemallthenecessaryargumentsbydefiningURLrules.Thefollowinglinesconfiguretheresourceroutingfortheapi.Opentheapi/views.pyfilecreatedearlierandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

api.add_resource(CategoryListResource,'/categories/')

api.add_resource(CategoryResource,'/categories/<int:id>')

api.add_resource(MessageListResource,'/messages/')

api.add_resource(MessageResource,'/messages/<int:id>')

Eachcalltotheapi.add_resourcemethodroutesaURLtoaresource,specificallytooneofthepreviouslydeclaredsubclassesoftheflask_restful.Resourceclass.WhenthereisarequesttotheAPIandtheURLmatchesoneoftheURLsspecifiedintheapi.add_resourcemethod,FlaskwillcallthemethodthatmatchestheHTTPverbintherequestforthespecifiedclass.

Page 268: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

RegisteringtheblueprintandrunningmigrationsCreateanewapp.pyfilewithintheapifolder.ThefollowinglinesshowthecodethatcreatesaFlaskapplication.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder.

fromflaskimportFlask

defcreate_app(config_filename):

app=Flask(__name__)

app.config.from_object(config_filename)

frommodelsimportdb

db.init_app(app)

fromviewsimportapi_bp

app.register_blueprint(api_bp,url_prefix='/api')

returnapp

Thecodeintheapi/app.pyfiledeclaresacreate_appfunctionthatreceivestheconfigurationfilenameintheconfig_filenameargument,setupsaFlaskappwiththisconfigurationfile,andreturnstheappobject.First,thefunctioncreatesthemainentrypointfortheFlaskapplicationnamedapp.Then,thecodecallstheapp.config.from_objectmethodwiththeconfig_filenamereceivedasanargument.Thisway,theFlaskappusesthevaluesthatarespecifiedinthevariablesdefinedinthePythonmodulereceivedasanargumenttosetupthesettingsfortheFlaskapp.

Thenextlinecallstheinit_appmethodfortheflask_sqlalchemy.SQLAlchemyinstancecreatedinthemodelsmodulenameddb.ThecodepassesappasanargumenttolinkthecreatedFlaskappwiththeSQLAlchemyinstance.

Thenextlinecallstheapp.register_blueprintmethodtoregistertheblueprintcreatedintheviewsmodule,namedapi_bp.Theurl_prefixargumentissetto'/api'becausewewanttheresourcestobeavailablewith/apiasaprefix.Nowhttp://localhost:5000/api/isgoingtobetheURLfortheAPIrunningontheFlaskdevelopmentserver.Finally,thefunctionreturnstheappobject.

Createanewrun.pyfilewithintheapifolder.Thefollowinglinesshowthecodethatusesthepreviouslydefinedcreate_appfunctiontocreateaFlaskapplicationandrunit.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder.

fromappimportcreate_app

Page 269: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

app=create_app('config')

if__name__=='__main__':

app.run(host=app.config['HOST'],

port=app.config['PORT'],

debug=app.config['DEBUG'])

Thecodeintheapi/run.pyfilecallsthecreate_appfunction,declaredintheappmodule,with'config'asanargument.ThefunctionwillsetupaFlaskappwiththismoduleastheconfigurationfile.

Thelastlinejustcallstheapp.runmethodtostarttheFlaskapplicationwiththehost,portanddebugvaluesreadfromtheconfigmodule.Thecodestartstheapplicationbycallingtherunmethodtoimmediatelylaunchalocalserver.Rememberthatwecouldalsoachievethesamegoalusingtheflaskcommand-linescript.

Createanewmigrate.pyfilewithintheapifolder.Thefollowinglinesshowthecodethatuseflask_scriptandflask_migratetorunmigrations.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

fromflask_scriptimportManager

fromflask_migrateimportMigrate,MigrateCommand

frommodelsimportdb

fromrunimportapp

migrate=Migrate(app,db)

manager=Manager(app)

manager.add_command('db',MigrateCommand)

if__name__=='__main__':

manager.run()

Thecodecreatesaninstanceofflask_migrate.MigratewiththeFlaskappcreatedinthepreviouslyexplainedrunmodule,app,andtheflask_sqlalchemy.SQLAlchemyinstancecreatedinthemodelsmodule,db.Then,thecodecreatesaflask_script.ManagerclasswiththeFlaskappasanargumentandsavesitsreferenceinthemanagervariable.Thenextlinecallstheadd_commandmethodwith'db'andMigrateCommandasarguments.ThemainfunctioncallstherunmethodfortheManagerinstance.

Thisway,aftertheextensioninitializes,thecodeaddsadbgrouptothecommand-lineoptions.Thedbgrouphasmanysub-commandsthatwewillusethroughthemigrate.pyscript.

Now,wewillrunthescriptstorunmigrationsandgeneratethenecessarytablesinthePostgreSQLdatabase.MakesureyourunthescriptsintheterminalorCommandPromptwindowinwhichyouhaveactivatedthevirtualenvironmentandthatyouarelocatedinthe

Page 270: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

apifolder.

Runthefirstscript,thatinitializesmigrationsupportfortheapplication.

pythonmigrate.pydbinit

Thefollowinglinesshowthesampleoutputgeneratedafterrunningthepreviousscript.Youroutputwillbedifferentaccordingtothebasefolderinwhichyouhavecreatedthevirtualenvironment:

Creatingdirectory/Users/gaston/PythonREST/Flask02/api/migrations...done

Creatingdirectory/Users/gaston/PythonREST/Flask02/api/migrations/versions

...done

Generating/Users/gaston/PythonREST/Flask02/api/migrations/alembic.ini...

done

Generating/Users/gaston/PythonREST/Flask02/api/migrations/env.py...done

Generating/Users/gaston/PythonREST/Flask02/api/migrations/README...done

Generating/Users/gaston/PythonREST/Flask02/api/migrations/script.py.mako...

done

Pleaseeditconfiguration/connection/loggingsettingsin

'/Users/gaston/PythonREST/Flask02/api/migrations/alembic.ini'before

proceeding.

Thescriptgeneratedanewmigrationssub-folderwithintheapifolderwithaversionssub-folderandmanyotherfiles.

Runthesecondscriptthatpopulatesthemigrationscriptwiththedetectedchangesinthemodels.Inthiscase,itisthefirsttimewepopulatethemigrationscript,andtherefore,themigrationscriptwillgeneratethetablesthatwillpersistourtwomodels:CategoryandMessage:

pythonmigrate.pydbmigrate

Thefollowinglinesshowthesampleoutputgeneratedafterrunningthepreviousscript.Youroutputwillbedifferentaccordingtothebasefolderinwhichyouhavecreatedthevirtualenvironment:

INFO[alembic.runtime.migration]ContextimplPostgresqlImpl.

INFO[alembic.runtime.migration]WillassumetransactionalDDL.

INFO[alembic.autogenerate.compare]Detectedaddedtable'category'

INFO[alembic.autogenerate.compare]Detectedaddedtable'message'

Generating

/Users/gaston/PythonREST/Flask02/api/migrations/versions/417543056ac3_.py...

done

Theoutputindicatesthattheapi/migrations/versions/417543056ac3_.pyfileincludesthecodetocreatethecategoryandmessagetables.Thefollowinglinesshowthecodeforthisfilethatwasautomaticallygeneratedbasedonthemodels.Notethatthefilenamewillbedifferentinyourconfiguration.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

Page 271: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"""emptymessage

RevisionID:417543056ac3

Revises:None

CreateDate:2016-08-0801:05:31.134631

"""

#revisionidentifiers,usedbyAlembic.

revision='417543056ac3'

down_revision=None

fromalembicimportop

importsqlalchemyassa

defupgrade():

###commandsautogeneratedbyAlembic-pleaseadjust!###

op.create_table('category',

sa.Column('id',sa.Integer(),nullable=False),

sa.Column('name',sa.String(length=150),nullable=False),

sa.PrimaryKeyConstraint('id'),

sa.UniqueConstraint('name')

)

op.create_table('message',

sa.Column('id',sa.Integer(),nullable=False),

sa.Column('message',sa.String(length=250),nullable=False),

sa.Column('duration',sa.Integer(),nullable=False),

sa.Column('creation_date',sa.TIMESTAMP(),

server_default=sa.text('CURRENT_TIMESTAMP'),nullable=False),

sa.Column('category_id',sa.Integer(),nullable=False),

sa.Column('printed_times',sa.Integer(),server_default='0',

nullable=False),

sa.Column('printed_once',sa.Boolean(),server_default='false',

nullable=False),

sa.ForeignKeyConstraint(['category_id'],['category.id'],

ondelete='CASCADE'),

sa.PrimaryKeyConstraint('id'),

sa.UniqueConstraint('message')

)

###endAlembiccommands###

defdowngrade():

###commandsautogeneratedbyAlembic-pleaseadjust!###

op.drop_table('message')

op.drop_table('category')

###endAlembiccommands###

Thecodedefinestwofunctions:upgradeanddowngrade.Theupgradefunctionrunsthenecessarycodetocreatethecategoryandmessagetablesbymakingcallstoalembic.op.create_table.Thedowngradefunctionrunsthenecessarycodetogobacktothepreviousversion.

Runthethirdscripttoupgradethedatabase:

Page 272: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

pythonmigrate.pydbupgrade

Thefollowinglinesshowthesampleoutputgeneratedafterrunningthepreviousscript:

INFO[alembic.runtime.migration]ContextimplPostgresqlImpl.

INFO[alembic.runtime.migration]WillassumetransactionalDDL.

INFO[alembic.runtime.migration]Runningupgrade->417543056ac3,empty

message

Thepreviousscriptcalledtheupgradefunctiondefinedintheautomaticallygeneratedapi/migrations/versions/417543056ac3_.pyscript.Don'tforgetthatthefilenamewillbedifferentinyourconfiguration.

Afterwerunthepreviousscripts,wecanusethePostgreSQLcommandlineoranyotherapplicationthatallowsustoeasilyverifythecontentsofthePostreSQLdatabasetocheckthetablesthatthemigrationgenerated.

Runthefollowingcommandtolistthegeneratedtables.Incasethedatabasenameyouareusingisnotnamedmessages,makesureyouusetheappropriatedatabasename.

psql--username=user_name--dbname=messages--command="\dt"

Thefollowinglinesshowtheoutputwithallthegeneratedtablenames:

Listofrelations

Schema|Name|Type|Owner

--------+-----------------+-------+-----------

public|alembic_version|table|user_name

public|category|table|user_name

public|message|table|user_name

(3rows)

SQLAlchemygeneratedthetables,theuniqueconstraints,andtheforeignkeysbasedontheinformationincludedinourmodels.

category:PersiststheCategorymodel.message:PersiststheMessagemodel.

ThefollowingcommandwillallowyoutocheckthecontentsofthefourtablesafterwecomposeandsendHTTPrequeststotheRESTfulAPIandmakeCRUDoperationstothetwotables.ThecommandsassumethatyouarerunningPostgreSQLonthesamecomputerinwhichyouarerunningthecommand:

psql--username=user_name--dbname=messages--command="SELECT*FROM

category;"

psql--username=user_name--dbname=messages--command="SELECT*FROMmessage;"

Tip

InsteadofworkingwiththePostgreSQLcommand-lineutility,youcanuseaGUItooltocheckthecontentsofthePostgreSQLdatabase.Youalsousealsothedatabasetoolsincluded

Page 273: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

inyourfavoriteIDEtocheckthecontentsfortheSQLitedatabase.

Alembicgeneratedanadditionaltablenamedalembic_versionthatsavestheversionnumberforthedatabaseintheversion_numcolumn.Thistablemakesispossibleforthemigrationscriptstoretrievethecurrentversionofthedatabaseandupgradeordowngradeitbasedonourneeds.

Page 274: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingandretrievingrelatedresourcesNow,wecanruntheapi/run.pyscriptthatlaunchesFlask'sdevelopment.Executethefollowingcommandintheapifolder.

pythonrun.py

Thefollowinglinesshowtheoutputafterweexecutetheprecedingcommand.Thedevelopmentserverislisteningatport5000.

*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)

*Restartingwithstat

*Debuggerisactive!

*Debuggerpincode:198-040-402

Now,wewillusetheHTTPiecommandoritscurlequivalentstocomposeandsendHTTPrequeststotheAPI.WewilluseJSONfortherequeststhatrequireadditionaldata.RememberthatyoucanperformthesametaskswithyourfavoriteGUI-basedtool.

First,wewillcomposeandsendHTTPrequeststocreatetwomessagecategories:

httpPOST:5000/api/categories/name='Information'

httpPOST:5000/api/categories/name='Warning'

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Information"}'

:5000/api/categories/

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Warning"}'

:5000/api/categories/

TheprecedingcommandswillcomposeandsendtwoPOSTHTTPrequestswiththespecifiedJSONkey-valuepair.Therequestsspecify/api/categories/,andtherefore,theywillmatchthe'/api'url_prefixfortheapi_bpblueprint.Then,therequestwillmatchthe'/categories/'URLroutefortheCategoryListresourceandruntheCategoryList.postmethod.Themethoddoesn'treceiveargumentsbecausetheURLroutedoesn'tincludeanyparameters.AstheHTTPverbfortherequestisPOST,Flaskcallsthepostmethod.IfthetwonewCategoryinstancesweresuccessfullypersistedinthedatabase,thetwocallswillreturnanHTTP201CreatedstatuscodeandtherecentlypersistedCategoryserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponseforthetwoHTTPrequests,withthenewCategoryobjectsintheJSONresponses.

NotethattheresponsesincludetheURL,url,forthecreatedcategories.Themessagesarrayisemptyinbothcasesbecausetherearen'tmessagesrelatedtoeachnewcategoryyet:

HTTP/1.0201CREATED

Content-Length:116

Content-Type:application/json

Date:Mon,08Aug201605:26:58GMT

Page 275: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Server:Werkzeug/0.11.10Python/3.5.1

{

"id":1,

"messages":[],

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

}

HTTP/1.0201CREATED

Content-Length:112

Content-Type:application/json

Date:Mon,08Aug201605:27:05GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"id":2,

"messages":[],

"name":"Warning",

"url":"http://localhost:5000/api/categories/2"

}

Now,wewillcomposeandsendHTTPrequeststocreatetwomessagesthatbelongtothefirstmessagecategorywerecentlycreated:Information.Wewillspecifythecategorykeywiththenameofthedesiredmessagecategory.ThedatabasetablethatpersiststheMessagemodelwillsavethevalueoftheprimarykeyoftherelatedCategorywhosenamevaluematchestheoneweprovide:

httpPOST:5000/api/messages/message='Checkingtemperaturesensor'duration=5

category="Information"

httpPOST:5000/api/messages/message='Checkinglightsensor'duration=8

category="Information"

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Checking

temperaturesensor","category":"Information"}':5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Checking

lightsensor","category":"Information"}':5000/api/messages/

ThefirstcommandwillcomposeandsendthefollowingHTTPrequest:POSThttp://localhost:5000/api/messages/withthefollowingJSONkey-valuepairs:

{

"message":"Checkingtemperaturesensor",

"category":"Information"

}

ThesecondcommandwillcomposeandsendthesameHTTPrequestwiththefollowingJSONkey-valuepairs:

{

"message":"Checkinglightsensor",

"category":"Information"

}

Page 276: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Therequestsspecify/api/categories/,andtherefore,theywillmatchthe'/api'url_prefixfortheapi_bpblueprint.Then,therequestwillmatchthe'/messages/'URLroutefortheMessageListresourceandruntheMessageList.postmethod.Themethoddoesn'treceiveargumentsbecausetheURLroutedoesn'tincludeanyparameters.AstheHTTPverbfortherequestisPOST,Flaskcallsthepostmethod.ThetheMessageSchema.process_categorymethodwillprocessthedataforthecategoryandtheMessageListResource.postmethodwillretrievetheCategorythatmatchesthespecifiedcategorynamefromthedatabase,touseitastherelatedcategoryforthenewmessage.IfthetwonewMessageinstancesweresuccessfullypersistedinthedatabase,thetwocallswillreturnanHTTP201CreatedstatuscodeandtherecentlypersistedMessageserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponseforthetwoHTTPrequests,withthenewMessageobjectsintheJSONresponses.NotethattheresponsesincludetheURL,url,forthecreatedmessages.Inaddition,theresponseincludestheid,name,andurlfortherelatedcategory.

HTTP/1.0201CREATED

Content-Length:369

Content-Type:application/json

Date:Mon,08Aug201615:18:43GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:18:43.260474+00:00",

"duration":5,

"id":1,

"message":"Checkingtemperaturesensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/1"

}

HTTP/1.0201CREATED

Content-Length:363

Content-Type:application/json

Date:Mon,08Aug201615:27:30GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:27:30.124511+00:00",

"duration":8,

"id":2,

"message":"Checkinglightsensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/2"

}

Page 277: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WecanruntheprecedingcommandstocheckthecontentsofthetablesthatthemigrationscreatedinthePostgreSQLdatabase.Wewillnoticethatthecategory_idcolumnforthemessagetablesavesthevalueoftheprimarykeyoftherelatedrowinthecategorytable.TheMessageSchemaclassusesafields.Nestedinstancetorendertheid,urlandnamefieldsfortherelatedCategory.ThefollowingscreenshotshowsthecontentsforthecategoryandthemessagetableinaPostgreSQLdatabaseafterrunningtheHTTPrequests:

Now,wewillcomposeandsendanHTTPrequesttoretrievethecategorythatcontainstwomessages,thatisthecategoryresourcewhoseidorprimarykeyisequalto1.Don'tforgettoreplace1withtheprimarykeyvalueofthecategorywhosenameisequalto'Information'inyourconfiguration:

http:5000/api/categories/1

Thefollowingistheequivalentcurlcommand:

curl-iXGET:5000/api/categories/1

TheprecedingcommandwillcomposeandsendaGETHTTPrequest.Therequesthasanumberafter/api/categories/,andtherefore,itwillmatch'/categories/<int:id>'andruntheCategoryResource.getmethod,thatis,thegetmethodfortheCategoryResourceclass.IfaCategoryinstancewiththespecifiedidexistsinthedatabase,thecalltothemethodwillwillreturnanHTTP200OKstatuscodeandtheCategoryinstanceserializedtoJSONintheresponsebody.TheCategorySchemaclassusesafields.Nestedinstancetorenderallthefieldsforallthemessagesrelatedtothecategoryexceptingthecategoryfield.Thefollowinglinesshowasampleresponse:

HTTP/1.0200OK

Content-Length:1078

Content-Type:application/json

Date:Mon,08Aug201616:09:10GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

Page 278: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"id":1,

"messages":[

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:27:30.124511+00:00",

"duration":8,

"id":2,

"message":"Checkinglightsensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/2"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:18:43.260474+00:00",

"duration":5,

"id":1,

"message":"Checkingtemperaturesensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/1"

}

],

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

}

Now,wewillcomposeandsendaPOSTHTTPrequesttocreateamessagerelatedtoacategorynamethatdoesn'texist:'Error':

httpPOST:5000/api/messages/message='Temperaturesensorerror'duration=10

category="Error"

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"

Temperaturesensorerror","category":"Error"}':5000/api/messages/

TheCategoryListResource.postmethodwon'tbeabletoretrieveaCategoryinstancewhosenameisequaltothespecifiedvalue,andtherefore,themethodwillcreateanewCategory,saveitanduseitastherelatedcategoryforthenewmessage.ThefollowinglinesshowanexampleresponsefortheHTTPrequest,withthenewMessageobjectintheJSONresponsesandthedetailsforthenewCategoryobjectrelatedtothemessage:

HTTP/1.0201CREATED

Content-Length:361

Page 279: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Content-Type:application/json

Date:Mon,08Aug201617:20:22GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"category":{

"id":3,

"name":"Error",

"url":"http://localhost:5000/api/categories/3"

},

"creation_date":"2016-08-08T14:20:22.103752+00:00",

"duration":10,

"id":3,

"message":"Temperaturesensorerror",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/3"

}

WecanrunthecommandsexplainedearliertocheckthecontentsofthetablesthatthemigrationscreatedinthePostgreSQLdatabase.Wewillnoticethatwehaveanewrowinthecategorytablewiththerecentlyaddedcategorywhenwecreatedanewmessage.ThefollowingscreenshotshowsthecontentsforthecategoryandmessagetablesinaPostgreSQLdatabaseafterrunningtheHTTPrequests:

Page 280: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Marshmallowis:

1. AlightweightlibraryforconvertingcomplexdatatypestoandfromnativePythondatatypes.

2. AnORM.3. AlightweightwebframeworkthatreplacesFlask.

2. SQLAlchemyis:1. AlightweightlibraryforconvertingcomplexdatatypestoandfromnativePython

datatypes.2. AnORM.3. AlightweightwebframeworkthatreplacesFlask.

3. Themarshmallow.pre_loaddecorator:1. RegistersamethodtorunafteranyinstanceoftheMessageSchemaclassiscreated.2. Registersamethodtoinvokeafterserializinganobject.3. Registersamethodtoinvokebeforedeserializinganobject.

4. ThedumpmethodforanyinstanceofaSchemasubclass:1. RoutesURLstoPythonprimitives.2. Persiststheinstanceorcollectionofinstancespassedasanargumenttothedatabase.3. Takestheinstanceorcollectionofinstancespassedasanargumentandappliesthe

fieldfilteringandoutputformattingspecifiedintheSchemasubclasstotheinstanceorcollectionofinstances.

5. Whenwedeclareanattributeasaninstanceofthemarshmallow.fields.Nestedclass:1. ThefieldwillnestasingleSchemaoracollectionofSchemabasedonthevaluefor

themanyargument.2. ThefieldwillnestasingleSchema.IfwewanttonestacollectionofSchema,wehave

touseaninstanceofthemarshmallow.fields.NestedCollectionclass.3. ThefieldwillnestacollectionofSchema.IfwewanttonestasingleSchema,wehave

touseaninstanceofthemarshmallow.fields.NestedSingleclass.

Page 281: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,weexpandedthecapabilitiesofthepreviousversionoftheRESTfulAPIthatwecreatedinthepreviouschapter.WeusedSQLAlchemyasourORMtoworkwithaPostgreSQLdatabase.Weinstalledmanypackagestosimplifymanycommontasks,wrotecodeforthemodelsandtheirrelationships,andworkedwithschemastovalidate,serialize,anddeserializethesemodels.

Wecombinedblueprintswithresourcefulroutingandwereabletogeneratethedatabasefromthemodels.WecomposedandsentmanyHTTPrequeststoourRESTfulAPIandanalyzedhoweachHTTPrequestwasprocessedinourcodeandhowthemodelspersistedinthedatabasetables.

NowthatwebuiltacomplexAPIwithFlask,Flask-RESTful,andSQLAlchemy,wewilluseadditionalfeaturesandaddsecurityandauthentication,whichiswhatwearegoingtodiscussinthenextchapter.

Page 282: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter7.ImprovingandAddingAuthenticationtoanAPIwithFlaskInthischapter,wewillimprovetheRESTfulAPIthatwestartedinthepreviouschapterandwewilladdauthenticationrelatedsecuritytoit.Wewill:

ImproveuniqueconstraintsinthemodelsUpdatefieldsforaresourcewiththePATCHmethodCodeagenericpaginationclassAddpaginationfeaturestotheAPIUnderstandthestepstoaddauthenticationandpermissionsAddausermodelCreateaschematovalidate,serializeanddeserializeusersAddauthenticationtoresourcesCreateresourceclassestohandleusersRunmigrationstogeneratetheusertableComposerequestswiththenecessaryauthentication

Page 283: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ImprovinguniqueconstraintsinthemodelsWhenwecreatedtheCategorymodel,wespecifiedtheTruevaluefortheuniqueargumentwhenwecreatedthedb.Columninstancenamedname.Asaresult,themigrationsgeneratedthenecessaryuniqueconstrainttomakesurethatthenamefieldhasuniquevaluesinthecategorytable.Thisway,thedatabasewon'tallowustoinsertduplicatevaluesforcategory.name.However,theerrormessagegeneratedwhenwetrytodosoisnotclear.

Runthefollowingcommandtocreateacategorywithaduplicatename.Thereisalreadyanexistingcategorywiththenameequalto'Information':

httpPOST:5000/api/categories/name='Information'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Information"}'

:5000/api/categories/

ThepreviouscommandwillcomposeandsendaPOSTHTTPrequestwiththespecifiedJSONkey-valuepair.Theuniqueconstraintinthecategory.namefieldwon'tallowthedatabasetabletopersistthenewcategory.Thus,therequestwillreturnanHTTP400BadRequeststatuscodewithanintegrityerrormessage.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BADREQUEST

Content-Length:282

Content-Type:application/json

Date:Mon,15Aug201603:53:27GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"error":"(psycopg2.IntegrityError)duplicatekeyvalueviolatesunique

constraint"category_name_key"\nDETAIL:Key(name)=(Information)

alreadyexists.\n[SQL:'INSERTINTOcategory(name)VALUES(%

(name)s)

RETURNINGcategory.id'][parameters:{'name':'Information'}]"

}

Obviously,theerrormessageisextremelytechnicalandprovidestoomanydetailsaboutthedatabaseandthequerythatfailed.Wemightparsetheerrormessagetoautomaticallygenerateamoreuserfriendlyerrormessage.However,insteadofdoingso,wewanttoavoidtryingtoinsertarowthatweknowwillfail.Wewilladdcodetomakesurethatacategoryisuniquebeforewetrytopersistit.Ofcourse,thereisstillachancetoreceivethepreviouslyshownerrorifsomebodyinsertsacategorywiththesamenamebetweenthetimewerunourcode,indicatingthatacategorynameisunique,andpersistthechangesinthedatabase.However,thechancesarelowerandwecanreducethechangesofthepreviouslyshownerrormessagetobeshown.

Tip

Page 284: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Inaproduction-readyRESTAPIweshouldneverreturntheerrormessagesreturnedbySQLAlchemyoranyotherdatabase-relateddata,asitmightincludesensitivedatathatwedon'twanttheusersofourAPItobeabletoretrieve.Inthiscase,wearereturningalltheerrorsfordebuggingpurposesandtobeabletoimproveourAPI.

Now,wewilladdanewclassmethodtotheCategoryclasstoallowustodeterminewhetheranameisuniqueornot.Opentheapi/models.pyfileandaddthefollowinglineswithinthedeclarationoftheCategoryclass.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

@classmethod

defis_unique(cls,id,name):

existing_category=cls.query.filter_by(name=name).first()

ifexisting_categoryisNone:

returnTrue

else:

ifexisting_category.id==id:

returnTrue

else:

returnFalse

ThenewCategory.is_uniqueclassmethodreceivestheidandthenameforthecategorythatwewanttomakesurethathasauniquename.Ifthecategoryisanewonethathasn'tbeensavedyet,wewillreceivea0fortheidvalue.Otherwise,wewillreceivethecategoryidintheargument.

Themethodcallsthequery.filter_bymethodforthecurrentclasstoretrieveacategorywhosenamematchestheothercategoryname.Incasethereisacategorythatmatchesthecriteria,themethodwillreturnTrueonlyiftheidisthesameonethantheonereceivedintheargument.Incasenocategorymatchesthecriteria,themethodwillreturnTrue.

WewillusethepreviouslycreatedclassmethodtocheckwhetheracategoryisuniqueornotbeforecreatingandpersistingitintheCategoryListResource.postmethod.Opentheapi/views.pyfileandreplacetheexistingpostmethoddeclaredintheCategoryListResourceclasswiththefollowinglines.Thelinesthathavebeenaddedormodifiedarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

defpost(self):

request_dict=request.get_json()

ifnotrequest_dict:

resp={'message':'Noinputdataprovided'}

returnresp,status.HTTP_400_BAD_REQUEST

errors=category_schema.validate(request_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

category_name=request_dict['name']

ifnotCategory.is_unique(id=0,name=category_name):

response={'error':'Acategorywiththesamenamealready

exists'}

returnresponse,status.HTTP_400_BAD_REQUEST

Page 285: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

try:

category=Category(category_name)

category.add(category)

query=Category.query.get(category.id)

result=category_schema.dump(query).data

returnresult,status.HTTP_201_CREATED

exceptSQLAlchemyErrorase:

db.session.rollback()

resp={"error":str(e)}

returnresp,status.HTTP_400_BAD_REQUEST

Now,wewillperformthesamevalidationintheCategoryResource.patchmethod.Opentheapi/views.pyfileandreplacetheexistingpatchmethoddeclaredintheCategoryResourceclasswiththefollowinglines.Thelinesthathavebeenaddedormodifiedarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

defpatch(self,id):

category=Category.query.get_or_404(id)

category_dict=request.get_json()

ifnotcategory_dict:

resp={'message':'Noinputdataprovided'}

returnresp,status.HTTP_400_BAD_REQUEST

errors=category_schema.validate(category_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

try:

if'name'incategory_dict:

category_name=category_dict['name']

ifCategory.is_unique(id=id,name=category_name):

category.name=category_name

else:

response={'error':'Acategorywiththesamename

already

exists'}

returnresponse,status.HTTP_400_BAD_REQUEST

category.update()

returnself.get(id)

exceptSQLAlchemyErrorase:

db.session.rollback()

resp={"error":str(e)}

returnresp,status.HTTP_400_BAD_REQUEST

Runthefollowingcommandtoagaincreateacategorywithaduplicatename:

httpPOST:5000/api/categories/name='Information'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"Information"}'

:5000/api/categories/

ThepreviouscommandwillcomposeandsendaPOSTHTTPrequestwiththespecifiedJSONkey-valuepair.Thechangeswemadewillgeneratearesponsewithauserfriendlyerrormessageandwillavoidtryingtopersistthechanges.TherequestwillreturnanHTTP400

Page 286: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

BadRequeststatuscodewiththeerrormessageintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BADREQUEST

Content-Length:64

Content-Type:application/json

Date:Mon,15Aug201604:38:43GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"error":"Acategorywiththesamenamealreadyexists"

}

Now,wewilladdanewclassmethodtotheMessageclasstoallowustodeterminewhetheramessageisuniqueornot.Opentheapi/models.pyfileandaddthefollowinglineswithinthedeclarationoftheMessageclass.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

@classmethod

defis_unique(cls,id,message):

existing_message=cls.query.filter_by(message=message).first()

ifexisting_messageisNone:

returnTrue

else:

ifexisting_message.id==id:

returnTrue

else:

returnFalse

ThenewMessage.is_uniqueclassmethodreceivestheidandthemessageforthemessagethatwewanttomakesurethathasauniquevalueforthemessagefield.Ifthemessageisanewonethathasn'tbeensavedyet,wewillreceivea0fortheidvalue.Otherwise,wewillreceivethemessageidintheargument.

Themethodcallsthequery.filter_bymethodforthecurrentclasstoretrieveamessagewhosemessagefieldmatchestheothermessage'smessage.Incasethereisamessagethatmatchesthecriteria,themethodwillreturnTrueonlyiftheidisthesameonethantheonereceivedintheargument.Incasenomessagematchesthecriteria,themethodwillreturnTrue.

WewillusethepreviouslycreatedclassmethodtocheckwhetheramessageisuniqueornotbeforecreatingandpersistingitintheMessageListResource.postmethod.Opentheapi/views.pyfileandreplacetheexistingpostmethoddeclaredintheMessageListResourceclasswiththefollowinglines.Thelinesthathavebeenaddedormodifiedarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

defpost(self):

request_dict=request.get_json()

ifnotrequest_dict:

response={'message':'Noinputdataprovided'}

returnresponse,status.HTTP_400_BAD_REQUEST

errors=message_schema.validate(request_dict)

Page 287: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

message_message=request_dict['message']

ifnotMessage.is_unique(id=0,message=message_message):

response={'error':'Amessagewiththesamemessagealready

exists'}

returnresponse,status.HTTP_400_BAD_REQUEST

try:

category_name=request_dict['category']['name']

category=Category.query.filter_by(name=category_name).first()

ifcategoryisNone:

#CreateanewCategory

category=Category(name=category_name)

db.session.add(category)

#Nowthatwearesurewehaveacategory

#createanewMessage

message=Message(

message=message_message,

duration=request_dict['duration'],

category=category)

message.add(message)

query=Message.query.get(message.id)

result=message_schema.dump(query).data

returnresult,status.HTTP_201_CREATED

exceptSQLAlchemyErrorase:

db.session.rollback()

resp={"error":str(e)}

returnresp,status.HTTP_400_BAD_REQUEST

Now,wewillperformthesamevalidationintheMessageResource.patchmethod.Opentheapi/views.pyfileandreplacetheexistingpatchmethoddeclaredintheMessageResourceclasswiththefollowinglines.Thelinesthathavebeenaddedormodifiedarehighlighted.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

defpatch(self,id):

message=Message.query.get_or_404(id)

message_dict=request.get_json(force=True)

if'message'inmessage_dict:

message_message=message_dict['message']

ifMessage.is_unique(id=id,message=message_message):

message.message=message_message

else:

response={'error':'Amessagewiththesamemessagealready

exists'}

returnresponse,status.HTTP_400_BAD_REQUEST

if'duration'inmessage_dict:

message.duration=message_dict['duration']

if'printed_times'inmessage_dict:

message.printed_times=message_dict['printed_times']

if'printed_once'inmessage_dict:

message.printed_once=message_dict['printed_once']

dumped_message,dump_errors=message_schema.dump(message)

ifdump_errors:

returndump_errors,status.HTTP_400_BAD_REQUEST

validate_errors=message_schema.validate(dumped_message)

Page 288: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ifvalidate_errors:

returnvalidate_errors,status.HTTP_400_BAD_REQUEST

try:

message.update()

returnself.get(id)

exceptSQLAlchemyErrorase:

db.session.rollback()

resp={"error":str(e)}

returnresp,status.HTTP_400_BAD_REQUEST

Runthefollowingcommandtocreateamessagewithaduplicatevalueforthemessagefield:

httpPOST:5000/api/messages/message='Checkingtemperaturesensor'

duration=25category="Information"

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Checking

temperaturesensor","duration":25,"category":"Information"}'

:5000/api/messages/

ThepreviouscommandwillcomposeandsendaPOSTHTTPrequestwiththespecifiedJSONkey-valuepair.Thechangeswemadewillgeneratearesponsewithauserfriendlyerrormessageandwillavoidtryingtopersistthechangesinthemessage.TherequestwillreturnanHTTP400BadRequeststatuscodewiththeerrormessageintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BADREQUEST

Content-Length:66

Content-Type:application/json

Date:Mon,15Aug201604:55:46GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"error":"Amessagewiththesamemessagealreadyexists"

}

Page 289: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UpdatingfieldsforaresourcewiththePATCHmethodAsweexplainedinChapter6,WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlask,ourAPIisabletoupdateasinglefieldforanexistingresource,andtherefore,weprovideanimplementationforthePATCHmethod.Forexample,wecanusethePATCHmethodtoupdateanexistingmessageandsetthevalueforitsprinted_onceandprinted_timesfieldstotrueand1.Wedon'twanttousethePUTmethodbecausethismethodismeanttoreplaceanentiremessage.ThePATCHmethodismeanttoapplyadeltatoanexistingmessage,andtherefore,itistheappropriatemethodtojustchangethevalueofthosetwofields.

Now,wewillcomposeandsendanHTTPrequesttoupdateanexistingmessage,specifically,toupdatethevalueoftheprinted_onceandprinted_timesfields.Becausewejustwanttoupdatetwofields,wewillusethePATCHmethodinsteadofPUT.Makesureyoureplace1withtheidorprimarykeyofanexistingmessageinyourconfiguration:

httpPATCH:5000/api/messages/1printed_once=trueprinted_times=1

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d'{"printed_once":"true",

"printed_times":1}':5000/api/messages/1

ThepreviouscommandwillcomposeandsendaPATCHHTTPrequestwiththefollowingspecifiedJSONkey-valuepairs:

{

"printed_once":true,

"printed_times":1

}

Therequesthasanumberafter/api/messages/,andtherefore,itwillmatch'/messages/<int:id>'andruntheMessageResource.patchmethod,thatis,thepatchmethodfortheMessageResourceclass.IfaMessageinstancewiththespecifiedidexists,thecodewillretrievethevaluesfortheprinted_timesandprinted_oncekeysintherequestdictionaryupdatetheMessageinstanceandvalidateit.

IftheupdatedMessageinstanceisvalid,thecodewillpersistthechangesinthedatabaseandthecalltothemethodwillreturnanHTTP200OKstatuscodeandtherecentlyupdatedMessageinstanceserializedtoJSONintheresponsebody.Thefollowinglinesshowasampleresponse:

HTTP/1.0200OK

Content-Length:368

Content-Type:application/json

Date:Tue,09Aug201622:38:39GMT

Server:Werkzeug/0.11.10Python/3.5.1

Page 290: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:18:43.260474+00:00",

"duration":5,

"id":1,

"message":"Checkingtemperaturesensor",

"printed_once":true,

"printed_times":1,

"url":"http://localhost:5000/api/messages/1"

}

WecanrunthecommandsexplainedinChapter6,WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlask,tocheckthecontentsofthetablesthatthemigrationscreatedinthePostgreSQLdatabase.Wewillnoticethattheprinted_timesandprinted_oncevalueshavebeenupdatedfortherowinthemessagetable.ThefollowingscreenshotshowsthecontentsfortheupdatedrowofthemessagetableinaPostgreSQLdatabaseafterrunningtheHTTPrequest.ThescreenshotshowstheresultsofexecutingthefollowingSQLquery:SELECT*FROMmessageWHEREid=1:

Page 291: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CodingagenericpaginationclassOurdatabasehasafewrowsforeachofthetablesthatpersistthemodelswehavedefined.However,afterwestartworkingwithourAPIinareal-lifeproductionenvironment,wewillhavehundredsofmessages,andtherefore,wewillhavetodealwithlargeresultsets.Thus,wewillcreateagenericpaginationclassandwewilluseittoeasilyspecifyhowwewantlargeresultssetstobesplitintoindividualpagesofdata.

First,wewillcomposeandsendHTTPrequeststocreate9messagesthatbelongtooneofthecategorieswehavecreated:Information.Thisway,wewillhaveatotalof12messagespersistedinthedatabase.Wehad3messagesandweadd9more.

httpPOST:5000/api/messages/message='Initializinglightcontroller'

duration=25category="Information"

httpPOST:5000/api/messages/message='Initializinglightsensor'duration=20

category="Information"

httpPOST:5000/api/messages/message='Checkingpressuresensor'duration=18

category="Information"

httpPOST:5000/api/messages/message='Checkinggassensor'duration=14

category="Information"

httpPOST:5000/api/messages/message='SettingADCresolution'duration=22

category="Information"

httpPOST:5000/api/messages/message='Settingsamplerate'duration=15

category="Information"

httpPOST:5000/api/messages/message='Initializingpressuresensor'

duration=18category="Information"

httpPOST:5000/api/messages/message='Initializinggassensor'duration=16

category="Information"

httpPOST:5000/api/messages/message='Initializingproximitysensor'

duration=5category="Information"

Thefollowingaretheequivalentcurlcommands:

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"

Initializinglightcontroller","duration":25,"category":"Information"}'

:5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Initializing

lightsensor","duration":20,"category":"Information"}':5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Checking

pressuresensor","duration":18,"category":"Information"}'

:5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Checkinggas

sensor","duration":14,"category":"Information"}':5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"SettingADC

resolution","duration":22,"category":"Information"}':5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Setting

samplerate","duration":15,"category":"Information"}':5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Initializing

pressuresensor","duration":18,"category":"Information"}'

:5000/api/messages/

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Initializing

gassensor","duration":16,"category":"Information"}':5000/api/messages/

Page 292: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

curl-iXPOST-H"Content-Type:application/json"-d'{"message":"Initializing

proximitysensor","duration":5,"category":"Information"}'

:5000/api/messages/

ThepreviouscommandswillcomposeandsendninePOSTHTTPrequestswiththespecifiedJSONkey-valuepairs.Therequestspecifies/api/messages/,andtherefore,itwillmatch'/messages/'andruntheMessageListResource.postmethod,thatis,thepostmethodfortheMessageListResourceclass.

Now,wehave12messagesinourdatabase.However,wedon'twanttoretrievethe12messageswhenwecomposeandsendaGETHTTPrequestto/api/messages/.Wewillcreateacustomizablegenericpaginationclasstoincludeamaximumof5resourcesineachindividualpageofdata.

Opentheapi/config.pyfileandaddthefollowinglinesthatdeclaretwovariablesthatconfiguretheglobalpaginationsettings.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

PAGINATION_PAGE_SIZE=5

PAGINATION_PAGE_ARGUMENT_NAME='page'

ThevalueforthePAGINATION_PAGE_SIZEvariablespecifiesaglobalsettingwiththedefaultvalueforthepagesize,alsoknownaslimit.ThevalueforthePAGINATION_PAGE_ARGUMENT_NAMEspecifiesaglobalsettingwiththedefaultvaluefortheargumentnamethatwewilluseinourrequeststospecifythepagenumberwewanttoretrieve.

Createanewhelpers.pyfilewithintheapifolder.ThefollowinglinesshowthecodethatcreatesanewPaginationHelperclass.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

fromflaskimporturl_for

fromflaskimportcurrent_app

classPaginationHelper():

def__init__(self,request,query,resource_for_url,key_name,schema):

self.request=request

self.query=query

self.resource_for_url=resource_for_url

self.key_name=key_name

self.schema=schema

self.results_per_page=current_app.config['PAGINATION_PAGE_SIZE']

self.page_argument_name=

current_app.config['PAGINATION_PAGE_ARGUMENT_NAME']

defpaginate_query(self):

#Ifnopagenumberisspecified,weassumetherequestwantspage#1

page_number=self.request.args.get(self.page_argument_name,1,

type=int)

paginated_objects=self.query.paginate(

Page 293: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

page_number,

per_page=self.results_per_page,

error_out=False)

objects=paginated_objects.items

ifpaginated_objects.has_prev:

previous_page_url=url_for(

self.resource_for_url,

page=page_number-1,

_external=True)

else:

previous_page_url=None

ifpaginated_objects.has_next:

next_page_url=url_for(

self.resource_for_url,

page=page_number+1,

_external=True)

else:

next_page_url=None

dumped_objects=self.schema.dump(objects,many=True).data

return({

self.key_name:dumped_objects,

'previous':previous_page_url,

'next':next_page_url,

'count':paginated_objects.total

})

ThePaginationHelperclassdeclaresaconstructor,thatis,the__init__methodthatreceivedmanyargumentsandusesthemtoinitializetheattributeswiththesamenames:

request:TheFlaskrequestobjectthatwillallowthepaginate_querymethodtoretrievethepagenumbervaluespecifiedwiththeHTTPrequest.query:TheSQLAlchemyquerythatthepaginate_querymethodhastopaginate.resource_for_url:Astringwiththeresourcenamethatthepaginate_querymethodwillusetogeneratethefullURLsforthepreviouspageandthenextpage.key_name:Astringwiththekeynamethatthepaginate_querymethodwillusetoreturntheserializedobjects.schema:TheFlask-MarshmallowSchemasubclassthatthepaginate_querymethodmustusetoserializetheobjects.

Inaddition,theconstructorreadsandsavesthevaluesfortheconfigurationvariablesweaddedtotheconfig.pyfileintheresults_per_pageandpage_argument_nameattributes.

Theclassdeclaresthepaginate_querymethod.First,thecoderetrievesthepagenumberspecifiedintherequestandsavesitinthepage_numbervariable.Incasenopagenumberisspecified,thecodeassumesthatrequestrequiresthefirstpage.Then,thecodecallstheself.query.paginatemethodtoretrievethepagenumberspecifiedbypage_numberofthepaginatedresultofobjectsfromthedatabase,withanumberofresultsperpageindicatedbythevalueoftheself.results_per_pageattribute.Thenextlinesavesthepaginateditemsfromthepaginated_object.itemsattributeintheobjectsvariable.

Ifthevalueforthepaginated_objects.has_prevattributeisTrue,itmeansthatthereisa

Page 294: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

previouspageavailable.Inthiscase,thecodecallstheflask.url_forfunctiontogeneratethefullURLforthepreviouspagewiththevalueoftheself.resource_for_urlattribute.The_externalargumentissettoTruebecausewewanttoprovidethefullURL.

Ifthevalueforthepaginated_objects.has_nextattributeisTrue,itmeansthatthereisanextpageavailable.Inthiscase,thecodecallstheflask.url_forfunctiontogeneratethefullURLforthenextpagewiththevalueoftheself.resource_for_urlattribute.

Then,thecodecallstheself.schema.dumpmethodtoserializethepartialresultspreviouslysavedintheobjectvariable,withthemanyargumentsettoTrue.Thedumped_objectsvariablesavesthereferencetothedataattributeoftheresultsreturnedbythecalltothedumpmethod.

Finally,themethodreturnsadictionarywiththefollowingkey-valuepairs:

self.key_name:Theserializedpartialresultssavedinthedumped_objectsvariable.'previous':ThefullURLforthepreviouspagesavedintheprevious_page_urlvariable.'previous':ThefullURLforthenextpagesavedinthenext_page_urlvariable.'count':Thetotalnumberofobjectsavailableinthecompleteresultsetretrievedfromthepaginated_objects.totalattribute.

Page 295: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AddingpaginationfeaturesOpentheapi/views.pyfileandreplacethecodefortheMessageListResource.getmethodwiththehighlightedlinesinthenextlisting.Inaddition,makesurethatyouaddtheimportstatement.Thecodefileforthesampleisincludedintherestful_python_chapter_07_01folder:

fromhelpersimportPaginationHelper

classMessageListResource(Resource):

defget(self):

pagination_helper=PaginationHelper(

request,

query=Message.query,

resource_for_url='api.messagelistresource',

key_name='results',

schema=message_schema)

result=pagination_helper.paginate_query()

returnresult

ThenewcodeforthegetmethodcreatesaninstanceofthepreviouslyexplainedPaginationHelperclassnamedpagination_helperwiththerequestobjectasthefirstargument.Thenamedargumentsspecifythequery,resource_for_url,key_name,andschemathatthePaginationHelperinstancehastousetoprovideapaginatedqueryresult.

Thenextlinecallsthepagination_helper.paginate_querymethodthatwillreturntheresultsofthepaginatedquerywiththepagenumberspecifiedintherequest.Finally,themethodreturnstheresultsofthecalltothismethodthatincludethepreviouslyexplaineddictionary.Inthiscase,thepaginatedresultsetwiththemessageswillberenderedasavalueofthe'results'key,specifiedinthekey_nameargument.

Now,wewillcomposeandsendanHTTPrequesttoretrieveallthemessages,specificallyanHTTPGETmethodto/api/messages/.

http:5000/api/messages/

Thefollowingistheequivalentcurlcommand:

curl-iXGET:5000/api/messages/

ThenewcodefortheMessageListResource.getmethodwillworkwithpaginationandtheresultwillprovideusthefirst5messages(resultskey),thetotalnumberofmessagesforthequery(countkey)andalinktothenext(nextkey)andprevious(previouskey)pages.Inthiscase,theresultsetisthefirstpage,andtherefore,thelinktothepreviouspage(previouskey)isnull.Wewillreceivea200OKstatuscodeintheresponseheaderandthe5messagesintheresultsarray:

HTTP/1.0200OK

Page 296: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Content-Length:2521

Content-Type:application/json

Date:Wed,10Aug201618:26:44GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"count":12,

"results":[

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:27:30.124511+00:00",

"duration":8,

"id":2,

"message":"Checkinglightsensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/2"

},

{

"category":{

"id":3,

"name":"Error",

"url":"http://localhost:5000/api/categories/3"

},

"creation_date":"2016-08-08T14:20:22.103752+00:00",

"duration":10,

"id":3,

"message":"Temperaturesensorerror",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/3"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-08T12:18:43.260474+00:00",

"duration":5,

"id":1,

"message":"Checkingtemperaturesensor",

"printed_once":true,

"printed_times":1,

"url":"http://localhost:5000/api/messages/1"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:18:26.648071+00:00",

Page 297: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"duration":25,

"id":4,

"message":"Initializinglightcontroller",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/4"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:16.174807+00:00",

"duration":20,

"id":5,

"message":"Initializinglightsensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/5"

}

],

"next":"http://localhost:5000/api/messages/?page=2",

"previous":null

}

InthepreviousHTTPrequest,wedidn'tspecifyanyvalueforthepageparameter,andthereforethepaginate_querymethodinthePaginationHelperclassrequeststhefirstpagetothepaginatedquery.IfwecomposeandsendthefollowingHTTPrequesttoretrievethefirstpageofallthemessagesbyspecifying1forthepagevalue,theAPIwillprovidethesameresultsshownbefore:

http':5000/api/messages/?page=1'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':5000/api/messages/?page=1'

Tip

ThecodeinthePaginationHelperclassconsidersthatfirstpageispagenumber1.Thus,wedon'tworkwithzero-basednumberingforpages.

Now,wewillcomposeandsendanHTTPrequesttoretrievethenextpage,thatis,thesecondpageforthemessages,specificallyanHTTPGETmethodto/api/messages/withthepagevaluesetto2.RememberthatthevalueforthenextkeyreturnedintheJSONbodyofthepreviousresultprovidesuswiththefullURLtothenextpage:

http':5000/api/messages/?page=2'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':5000/api/messages/?page=2'

Page 298: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Theresultwillprovideusthesecondsetofthefivemessageresource(resultskey),thetotalnumberofmessagesforthequery(countkey),alinktothenext(nextkey),andprevious(previouskey)pages.Inthiscase,theresultsetisthesecondpage,andtherefore,thelinktothepreviouspage(previouskey)ishttp://localhost:5000/api/messages/?page=1.Wewillreceivea200OKstatuscodeintheresponseheaderandthe5messagesintheresultsarray.

HTTP/1.0200OK

Content-Length:2557

Content-Type:application/json

Date:Wed,10Aug201619:51:50GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"count":12,

"next":"http://localhost:5000/api/messages/?page=3",

"previous":"http://localhost:5000/api/messages/?page=1",

"results":[

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:22.335600+00:00",

"duration":18,

"id":6,

"message":"Checkingpressuresensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/6"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:26.189009+00:00",

"duration":14,

"id":7,

"message":"Checkinggassensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/7"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:29.854576+00:00",

"duration":22,

"id":8,

"message":"SettingADCresolution",

"printed_once":false,

Page 299: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

"printed_times":0,

"url":"http://localhost:5000/api/messages/8"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:33.838977+00:00",

"duration":15,

"id":9,

"message":"Settingsamplerate",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/9"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:37.830843+00:00",

"duration":18,

"id":10,

"message":"Initializingpressuresensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/10"

}

]

}

Finally,wewillcomposeandsendanHTTPrequesttoretrievethelastpage,thatis,thethirdpageforthemessages,specificallyanHTTPGETmethodto/api/messages/withthepagevaluesetto3.RememberthatthevalueforthenextkeyreturnedintheJSONbodyofthepreviousresultprovidesuswiththeURLtothenextpage:

http':5000/api/messages/?page=3'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':5000/api/messages/?page=3'

Theresultwillprovideusthelastsetwithtwomessageresources(resultskey),thetotalnumberofmessagesforthequery(countkey),alinktothenext(nextkey),andprevious(previouskey)pages.Inthiscase,theresultsetisthelastpage,andtherefore,thelinktothenextpage(nextkey)isnull.Wewillreceivea200OKstatuscodeintheresponseheaderandthe2messagesintheresultsarray:

HTTP/1.0200OK

Content-Length:1090

Content-Type:application/json

Page 300: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Date:Wed,10Aug201620:02:00GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"count":12,

"next":null,

"previous":"http://localhost:5000/api/messages/?page=2",

"results":[

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:41.645628+00:00",

"duration":16,

"id":11,

"message":"Initializinggassensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/11"

},

{

"category":{

"id":1,

"name":"Information",

"url":"http://localhost:5000/api/categories/1"

},

"creation_date":"2016-08-09T20:19:45.304391+00:00",

"duration":5,

"id":12,

"message":"Initializingproximitysensor",

"printed_once":false,

"printed_times":0,

"url":"http://localhost:5000/api/messages/12"

}

]

}

Page 301: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthestepstoaddauthenticationandpermissionsOurcurrentversionoftheAPIprocessesalltheincomingrequestswithoutrequiringanykindofauthentication.WewilluseaFlaskextensionandotherpackagestouseanHTTPauthenticationschemetoidentifytheuserthatoriginatedtherequestorthetokenthatsignedtherequest.Then,wewillusethesecredentialstoapplythepermissionsthatwilldeterminewhethertherequestmustbepermittedornot.Unluckily,neitherFlasknorFlask-RESTfulprovidesanauthenticationframeworkthatwecaneasilyplugandconfigure.Thus,wewillhavetowritecodetoperformmanytasksrelatedtoauthenticationandpermissions.

Wewanttobeabletocreateanewuserwithoutanyauthentication.However,alltheotherAPIcallsareonlygoingtobeavailableforauthenticatedusers.

First,wewillinstallaFlaskextensiontomakeiteasierforustoworkwithHTTPauthentication,Flask-HTTPAuth,andapackagetoallowustohashapasswordandcheckwhetheraprovidedpasswordisvalidornot,passlib.

WewillcreateanewUsermodelthatwillrepresentauser.Themodelwillprovidemethodstoallowustohashapasswordandverifywhetherapasswordprovidedforauserisvalidornot.WewillcreateaUserSchemaclasstospecifyhowwewanttoserializeanddeserializeauser.

Then,wewillconfiguretheFlaskextensiontoworkwithourUsermodeltoverifypasswordsandsettheauthenticateduserassociatedwitharequest.Wewillmakechangestotheexistingresourcestorequireauthenticationandwewillnewresourcestoallowustoretrieveexistingusersandcreateanewone.Finally,wewillconfiguretheroutesfortheresourcesrelatedtousers.

Oncewehavecompletedthepreviouslymentionedtasks,wewillrunmigrationstogeneratethenewtablethatpersiststheusersinthedatabase.Then,wewillcomposeandsendHTTPrequeststounderstandhowtheauthenticationandpermissionsworkwithournewversionoftheAPI.

MakesureyouquittheFlaskdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheterminaloraCommandPromptwindowinwhichitisrunning.ItistimetorunmanycommandsthatwillbethesameforeithermacOS,Linux,orWindows.Wecaninstallallthenecessarypackageswithpipwithasinglecommand.However,wewillruntwoindependentcommandstomakeiteasiertodetectanyproblemsincaseaspecificinstallationfails.

Now,wemustrunthefollowingcommandtoinstallFlask-HTTPAuthwithpip.ThispackagemakesiteasytoaddbasicHTTPauthenticationtoanyFlaskapplication:

pipinstallFlask-HTTPAuth

Page 302: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ThelastlinesfortheoutputwillindicatetheFlask-HTTPAuthpackagehasbeensuccessfullyinstalled:

Installingcollectedpackages:Flask-HTTPAuth

Runningsetup.pyinstallforFlask-HTTPAuth

SuccessfullyinstalledFlask-HTTPAuth-3.2.1

Runthefollowingcommandtoinstallpasslibwithpip.Thispackageisapopularonethatprovidesacomprehensivepasswordhashingframeworkthatsupportsmorethan30schemes.Wedefinitelydon'twanttowriteourownerror-proneandprobablyhighlyinsecurepasswordhashingcode,andtherefore,wewilltakeadvantageofalibrarythatprovidestheseservices:

pipinstallpasslib

Thelastlinesfortheoutputwillindicatethepasslibpackagehasbeensuccessfullyinstalled:

Installingcollectedpackages:passlib

Successfullyinstalledpasslib-1.6.5

Page 303: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AddingausermodelNow,wewillcreatethemodelthatwewillusetorepresentandpersisttheuser.Opentheapi/models.pyfileandaddthefollowinglinesafterthedeclarationoftheAddUpdateDeleteclass.Makesurethatyouaddtheimportstatements.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

frompasslib.appsimportcustom_app_contextaspassword_context

importre

classUser(db.Model,AddUpdateDelete):

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

name=db.Column(db.String(50),unique=True,nullable=False)

#Isavethehashedpassword

hashed_password=db.Column(db.String(120),nullable=False)

creation_date=db.Column(db.TIMESTAMP,

server_default=db.func.current_timestamp(),nullable=False)

defverify_password(self,password):

returnpassword_context.verify(password,self.hashed_password)

defcheck_password_strength_and_hash_if_ok(self,password):

iflen(password)<8:

return'Thepasswordistooshort',False

iflen(password)>32:

return'Thepasswordistoolong',False

ifre.search(r'[A-Z]',password)isNone:

return'Thepasswordmustincludeatleastoneuppercaseletter',

False

ifre.search(r'[a-z]',password)isNone:

return'Thepasswordmustincludeatleastonelowercaseletter',

False

ifre.search(r'\d',password)isNone:

return'Thepasswordmustincludeatleastonenumber',False

ifre.search(r"[!#$%&'()*+,-./[\\\]^_`{|}~"+r'"]',password)isNone:

return'Thepasswordmustincludeatleastonesymbol',False

self.hashed_password=password_context.encrypt(password)

return'',True

def__init__(self,name):

self.name=name

ThecodedeclarestheUsermodel,specificallyasubclassesofboththedb.ModelandtheAddUpdateDeleteclasses.Wespecifiedthefieldtypes,maximumlengthsanddefaultsforthefollowingthreeattributes-id,name,hashed_passwordandcreation_date.Theseattributesrepresentfieldswithoutanyrelationship,andtherefore,theyareinstancesofthedb.Columnclass.ThemodeldeclaresanidattributeandspecifiestheTruevaluefortheprimary_keyargumenttoindicateitistheprimarykey.SQLAlchemywillusethedatatogeneratethenecessarytableinthePostgreSQLdatabase.

Page 304: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TheUserclassdeclaresthefollowingmethods:

check_password_strength_and_hash_if_ok:Thismethodusestheremodulethatprovidesregularexpressionmatchingoperationstocheckwhetherthepasswordreceivedasanargumentfulfilsmanyqualitativerequirements.Thecoderequiresthepasswordtobelongerthaneightcharacters,withamaximumof32characters.Thepasswordmustincludeatleastoneuppercaseletter,onelowercaseletter,onenumber,andonesymbol.Thecodecheckstheresultsofmanycallstothere.searchmethodtodeterminewhetherthereceivedpasswordfulfilseachrequirement.Incaseanyoftherequirementsisn'tfulfilled,thecodereturnsatuplewithanerrormessageandFalse.Otherwise,thecodecallstheencryptmethodforthepasslib.apps.custom_app_contextinstanceimportedaspassword_context,withthereceivedpasswordasanargument.Theencryptmethodchoosesareasonablystrongschemebasedontheplatform,withthedefaultsettingsforroundsselectionandthecodesavesthehashedpasswordinthehash_passwordattribute.Finally,thecodereturnsatuplewithanemptystringandTrue,indicatingthatthepasswordfulfilledthequalitativerequirementsanditwashashed.

Tip

Bydefault,thepassliblibrarywillusetheSHA-512schemefor64-bitplatformsandSHA-256for32-bitplatforms.Inaddition,theminimumnumberofroundswillbesetto535,000.Wewillusethedefaultconfigurationvaluesforthisexample.However,youmusttakeintoaccountthatthesevaluesmightrequiretoomuchprocessingtimeforeachrequestthathastovalidatethepassword.Youshoulddefinitelyselectthemostappropriatealgorithmandnumberofroundsbasedonyoursecurityrequirements.

verify_password:Thismethodcallstheverifymethodforthepasslib.apps.custom_app_contextinstanceimportedaspassword_context,withthereceivedpasswordandthestoredhashedpasswordfortheuser,self.hashed_password,asthearguments.TheverifymethodhashesthereceivedpasswordandreturnsTrueonlyifthehashedreceivedpasswordmatchesthestoredhashedpassword.Weneverrestorethesavedpasswordtoitsoriginalstate.Wejustcomparehashedvalues.

Themodeldeclaresaconstructor,thatis,the__init__method.Thisconstructorreceivestheusernameinthenameargumentandsavesitinanattributewiththesamename.

Page 305: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Creatingaschemastovalidate,serialize,anddeserializeusersNow,wewillcreatetheFlask-Marshmallowschemathatwewillusetovalidate,serializeanddeserializethepreviouslydeclaredUsermodel.Opentheapi/models.pyfileandaddthefollowingcodeaftertheexistinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

classUserSchema(ma.Schema):

id=fields.Integer(dump_only=True)

name=fields.String(required=True,validate=validate.Length(3))

url=ma.URLFor('api.userresource',id='<id>',_external=True)

ThecodedeclarestheUserSchemaschema,specificallyasubclassofthema.Schemaclass.Rememberthatthepreviouscodewewrotefortheapi/models.pyfilecreatedaflask_marshmallow.Mashmallowinstancenamedma.

Wedeclaretheattributesthatrepresentfieldsasinstancesoftheappropriateclassdeclaredinthemarshmallow.fieldsmodule.TheUserSchemaclassdeclaresthenameattributeasaninstanceoffields.String.TherequiredargumentissettoTruetospecifythatthefieldcannotbeanemptystring.Thevalidateargumentissettovalidate.Length(3)tospecifythatthefieldmusthaveaminimumlengthof3characters.

Thevalidationforthepasswordisn'tincludedintheschema.Wewillusethecheck_password_strength_and_hash_if_okmethoddefinedintheUserclasstovalidatethepassword.

Page 306: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

AddingauthenticationtoresourcesWewillconfiguretheFlask-HTTPAuthextensiontoworkwithourUsermodeltoverifypasswordsandsettheauthenticateduserassociatedwitharequest.Wewilldeclareacustomfunctionthatthisextensionwilluseasacallbacktoverifyapassword.Wewillcreateanewbaseclassforourresourcesthatwillrequireauthentication.Opentheapi/views.pyfileandaddthefollowingcodeafterthelastlinethatusestheimportstatementandbeforethelinesthatdeclarestheBlueprintinstance.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

fromflask_httpauthimportHTTPBasicAuth

fromflaskimportg

frommodelsimportUser,UserSchema

auth=HTTPBasicAuth()

@auth.verify_password

defverify_user_password(name,password):

user=User.query.filter_by(name=name).first()

ifnotuserornotuser.verify_password(password):

returnFalse

g.user=user

returnTrue

classAuthRequiredResource(Resource):

method_decorators=[auth.login_required]

First,wecreateaninstanceoftheflask_httpauth.HTTPBasicAuthclassnamedauth.Then,wedeclaretheverify_user_passwordfunctionthatreceivesanameandapasswordasarguments.Thefunctionusesthe@auth.verify_passworddecoratortomakethisfunctionbecomethecallbackthatFlask-HTTPAuthwillusetoverifythepasswordforaspecificuser.Thefunctionretrievestheuserwhosenamematchesthenamespecifiedintheargumentandsavesitsreferenceintheuservariable.Ifauserisfound,thecodecheckstheresultsoftheuser.verify_passwordmethodwiththereceivedpasswordasanargument.

Ifeitherauserisn'tfoundorthecalltouser.verify_passwordreturnsFalse,thefunctionreturnsFalseandtheauthenticationwillfail.Ifthecalltouser.verify_passwordreturnsTrue,thefunctionstorestheauthenticatedUserinstanceintheuserattributefortheflask.gobject.

Tip

Theflask.gobjectisaproxythatallowsustostoreonthiswhateverwewanttoshareforonerequestonly.Theuserattributeweaddedtotheflask.gobjectwillbeonlyvalidfortheactiverequestanditwillreturndifferentvaluesforeachdifferentrequest.Thisway,itis

Page 307: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

possibletouseflask.g.userinanotherfunctionormethodcalledduringarequesttoaccessdetailsabouttheauthenticateduserfortherequest.

Finally,wedeclaredtheAuthRequiredResourceclassasasubclassofflask_restful.Resource.Wejustspecifiedauth.login_requiredasoneofthemembersofthelistthatweassigntothemethod_decoratorspropertyinheritedfromthebaseclass.Thisway,allthemethodsdeclaredinaresourcethatusesthenewAuthRequiredResourceclassasitssuperclasswillhavetheauth.login_requireddecoratorappliedtothem,andtherefore,anymethodthatiscalledtotheresourcewillrequireauthentication.

Now,wewillreplacethebaseclassfortheexistingresourceclassestomaketheminheritfromAuthRequiredResourceinsteadofResource.Wewantanyoftherequeststhatretrieveormodifycategoriesandmessagestobeauthenticated.

Thefollowinglinesshowthedeclarationsforthefourresourceclasses:

classMessageResource(Resource):

classMessageListResource(Resource):

classCategoryResource(Resource):

classCategoryListResource(Resource):

Opentheapi/views.pyfileandreplaceResourcebyAuthRequiredResourceinthepreviouslyshownfourlinesthatdeclaretheresourceclasses.Thefollowinglinesshowthenewcodeforeachresourceclassdeclaration:

classMessageResource(AuthRequiredResource):

classMessageListResource(AuthRequiredResource):

classCategoryResource(AuthRequiredResource):

classCategoryListResource(AuthRequiredResource):

Page 308: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingresourceclassestohandleusersWejustwanttobeabletocreateusersandusethemtoauthenticaterequests.Thus,wewilljustfocusoncreatingresourceclasseswithjustafewmethods.Wewon'tcreateacompleteusermanagementsystem.

Wewillcreatetheresourceclassesthatrepresenttheuserandthecollectionofusers.First,wewillcreateaUserResourceclassthatwewillusetorepresentauserresource.Opentheapi/views.pyfileandaddthefollowinglinesafterthelinethatcreatestheApiinstance.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

classUserResource(AuthRequiredResource):

defget(self,id):

user=User.query.get_or_404(id)

result=user_schema.dump(user).data

returnresult

TheUserResourceclassisasubclassofthepreviouslycodedAuthRequiredResourceanddeclaresagetmethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource.Themethodreceivestheidoftheuserthathastoberetrievedintheidargument.ThecodecallstheUser.query.get_or_404methodtoreturnanHTTP404NotFoundstatusincasethereisnouserwiththerequestedidintheunderlyingdatabase.Incasetheuserexists,thecodecallstheuser_schema.dumpmethodwiththeretrieveduserasanargumenttousetheUserSchemainstancetoserializetheUserinstancewhoseidmatchesthespecifiedid.ThedumpmethodtakestheUserinstanceandappliesthefieldfilteringandoutputformattingspecifiedintheUserSchemaclass.Thefieldfilteringspecifiesthatwedon'twantthehashedpasswordtobeserialized.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessageinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.

Now,wewillcreateaUserListResourceclassthatwewillusetorepresentthecollectionofusers.Opentheapi/views.pyfileandaddthefollowinglinesafterthecodethatcreatestheUserResourceclass.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

classUserListResource(Resource):

@auth.login_required

defget(self):

pagination_helper=PaginationHelper(

request,

query=User.query,

resource_for_url='api.userlistresource',

key_name='results',

schema=user_schema)

result=pagination_helper.paginate_query()

returnresult

defpost(self):

Page 309: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

request_dict=request.get_json()

ifnotrequest_dict:

response={'user':'Noinputdataprovided'}

returnresponse,status.HTTP_400_BAD_REQUEST

errors=user_schema.validate(request_dict)

iferrors:

returnerrors,status.HTTP_400_BAD_REQUEST

name=request_dict['name']

existing_user=User.query.filter_by(name=name).first()

ifexisting_userisnotNone:

response={'user':'Anuserwiththesamenamealreadyexists'}

returnresponse,status.HTTP_400_BAD_REQUEST

try:

user=User(name=name)

error_message,password_ok=\

user.check_password_strength_and_hash_if_ok(request_dict['password'])

ifpassword_ok:

user.add(user)

query=User.query.get(user.id)

result=user_schema.dump(query).data

returnresult,status.HTTP_201_CREATED

else:

return{"error":error_message},status.HTTP_400_BAD_REQUEST

exceptSQLAlchemyErrorase:

db.session.rollback()

resp={"error":str(e)}

returnresp,status.HTTP_400_BAD_REQUEST

TheUserListResourceclassisasubclassofflask_restful.Resourcebecausewedon'twantallthemethodstorequireauthentication.Wewanttobeabletocreateanewuserwithoutbeingauthenticated,andtherefore,[email protected]_requireddecoratoronlyforthegetmethod.Thepostmethoddoesn'trequireauthentication.TheclassdeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestontherepresentedresource:

get:ThismethodreturnsalistwithalltheUserinstancessavedinthedatabase.First,thecodecallstheUser.query.allmethodtoretrievealltheUserinstancespersistedinthedatabase.Then,thecodecallstheuser_schema.dumpmethodwiththeretrievedmessagesandthemanyargumentsettoTruetoserializetheiterablecollectionofobjects.ThedumpmethodwilltakeeachUserinstanceretrievedfromthedatabaseandapplythefieldfilteringandoutputformattingspecifiedtheCategorySchemaclass.Thecodereturnsthedataattributeoftheresultreturnedbythedumpmethod,thatis,theserializedmessagesinJSONformatasthebody,withthedefaultHTTP200OKstatuscode.post:Thismethodretrievesthekey-valuepairsreceivedintheJSONbody,createsanewUserinstanceandpersistsitinthedatabase.First,thecodecallstherequest.get_jsonmethodtoretrievethekey-valuepairsreceivedasargumentswiththerequest.Then,thecodecallstheuser_schema.validatemethodtovalidatethenewuserbuiltwiththeretrievedkey-valuepairs.Inthiscase,thecalltothismethodwilljustvalidatethenamefieldfortheuser.Incasetherewerevalidationerrors,thecodereturnsthevalidationerrorsandanHTTP400BadRequeststatus.Ifthevalidationissuccessful,thecode

Page 310: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

checkswhetheranuserwiththesamenamealreadyexistsinthedatabaseornottoreturnanappropriateerrorforthefieldthatmustbeunique.Iftheusernameisunique,thecodecreatesanewuserwiththespecifiednameandcallsitscheck_password_strength_and_hash_if_okmethod.Iftheprovidedpasswordfulfilsallthequalityrequirements,thecodepersiststheuserwithitshashedpasswordinthedatabase.Finally,thecodereturnstheserializedsaveduserinJSONformatasthebody,withtheHTTP201Createdstatuscode:

ThefollowingtableshowsthemethodofourpreviouslycreatedclassesrelatedtousresthatwewanttobeexecutedforeachcombinationofHTTPverbandscope.

HTTPverb Scope Classandmethod Requiresauthentication

GET Collectionofusers UserListResource.get Yes

GET User UserResource.get Yes

POST Collectionofusers UserListResource.post No

WemustmakethenecessaryresourceroutingconfigurationstocalltheappropriatemethodsandpassthemallthenecessaryargumentsbydefiningURLrules.Thefollowinglinesconfiguretheresourceroutingfortheuserrelatedresourcestotheapi.Opentheapi/views.pyfileandaddthefollowinglinesattheendofthecode.Thecodefileforthesampleisincludedintherestful_python_chapter_07_02folder:

api.add_resource(UserListResource,'/users/')

api.add_resource(UserResource,'/users/<int:id>')

Eachcalltotheapi.add_resourcemethodroutesaURLtooneofthepreviouslycodeduserrelatedresources.WhenthereisarequesttotheAPIandtheURLmatchesoneoftheURLsspecifiedintheapi.add_resourcemethod,FlaskwillcallthemethodthatmatchestheHTTPverbintherequestforthespecifiedclass.

Page 311: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

RunningmigrationstogeneratetheusertableNow,wewillrunmanyscriptstorunmigrationsandgeneratethenecessarytableinthePostgreSQLdatabase.MakesureyourunthescriptsintheterminalortheCommandPromptwindowinwhichyouhaveactivatedthevirtualenvironmentandthatyouarelocatedintheapifolder.

Runthefirstscriptthatpopulatesthemigrationscriptwiththedetectedchangesinthemodels.Inthiscase,itisthesecondtimewepopulatethemigrationscript,andtherefore,themigrationscriptwillgeneratethenewtablethatwillpersistournewUsermodel:model:

pythonmigrate.pydbmigrate

Thefollowinglinesshowthesampleoutputgeneratedafterrunningthepreviousscript.Youroutputwillbedifferentaccordingtothebasefolderinwhichyouhavecreatedthevirtualenvironment.

INFO[alembic.runtime.migration]ContextimplPostgresqlImpl.

INFO[alembic.runtime.migration]WillassumetransactionalDDL.

INFO[alembic.autogenerate.compare]Detectedaddedtable'user'

INFO[alembic.ddl.postgresql]Detectedsequencenamed'message_id_seq'as

ownedbyintegercolumn'message(id)',assumingSERIALandomitting

Generating

/Users/gaston/PythonREST/Flask02/api/migrations/versions/c8c45e615f6d_.py

...done

Theoutputindicatesthattheapi/migrations/versions/c8c45e615f6d_.pyfileincludesthecodetocreatetheusertables.Thefollowinglinesshowthecodeforthisfilethatwasautomaticallygeneratedbasedonthemodels.Noticethatthefilenamewillbedifferentinyourconfiguration.Thecodefileforthesampleisincludedintherestful_python_chapter_06_01folder:

"""emptymessage

RevisionID:c8c45e615f6d

Revises:417543056ac3

CreateDate:2016-08-1117:31:44.989313

"""

#revisionidentifiers,usedbyAlembic.

revision='c8c45e615f6d'

down_revision='417543056ac3'

fromalembicimportop

importsqlalchemyassa

defupgrade():

###commandsautogeneratedbyAlembic-pleaseadjust!###

op.create_table('user',

Page 312: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

sa.Column('id',sa.Integer(),nullable=False),

sa.Column('name',sa.String(length=50),nullable=False),

sa.Column('hashed_password',sa.String(length=120),nullable=False),

sa.Column('creation_date',sa.TIMESTAMP(),

server_default=sa.text('CURRENT_TIMESTAMP'),nullable=False),

sa.PrimaryKeyConstraint('id'),

sa.UniqueConstraint('name')

)

###endAlembiccommands###

defdowngrade():

###commandsautogeneratedbyAlembic-pleaseadjust!###

op.drop_table('user')

###endAlembiccommands###

Thecodedefinestwofunctions:upgradeanddowngrade.Theupgradefunctionrunsthenecessarycodetocreatetheusertablebymakingcallstoalembic.op.create_table.Thedowngradefunctionrunsthenecessarycodetogobacktothepreviousversion.

Runthesecondscripttoupgradethedatabase:

pythonmigrate.pydbupgrade

Thefollowinglinesshowthesampleoutputgeneratedafterrunningthepreviousscript:

INFO[alembic.runtime.migration]ContextimplPostgresqlImpl.

INFO[alembic.runtime.migration]WillassumetransactionalDDL.

INFO[alembic.runtime.migration]Runningupgrade417543056ac3->

c8c45e615f6d,emptymessage

Thepreviousscriptcalledtheupgradefunctiondefinedintheautomaticallygeneratedapi/migrations/versions/c8c45e615f6d_.pyscript.Don'tforgetthatthefilenamewillbedifferentinyourconfiguration.

Afterwerunthepreviousscripts,wecanusethePostgreSQLcommandlineoranyotherapplicationthatallowsustoeasilyverifythecontentsofthePostreSQLdatabasetocheckthenewtablethatthemigrationgenerated.Runthefollowingcommandtolistthegeneratedtables.Incasethedatabasenameyouareusingisnotnamedmessages,makesureyouusetheappropriatedatabasename:

psql--username=user_name--dbname=messages--command="\dt"

Thefollowinglinesshowtheoutputwithallthegeneratedtablenames.Themigrationsupgradegenerateanewtablenameduser.

Listofrelations

Schema|Name|Type|Owner

Page 313: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

--------+-----------------+-------+-----------

public|alembic_version|table|user_name

public|category|table|user_name

public|message|table|user_name

public|user|table|user_name

(4rows)

SQLAlchemygeneratedtheusertablewithitsprimarykey,itsuniqueconstraintonthenamefieldandthepasswordfieldbasedontheinformationincludedinourUsermodel.

ThefollowingcommandwillallowyoutocheckthecontentsoftheusertableafterwecomposeandsendHTTPrequeststotheRESTfulAPIandcreatenewusers.ThecommandsassumethatyouarerunningPostgreSQLonthesamecomputerinwhichyouarerunningthecommand:

psql--username=user_name--dbname=messages--command="SELECT*FROM

public.user;"

Now,wecanruntheapi/run.pyscriptthatlaunchesFlask'sdevelopment.Executethefollowingcommandintheapifolder:

pythonrun.py

Afterweexecutethepreviouscommand,thedevelopmentserverwillstartlisteningatport5000.

Page 314: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ComposingrequestswiththenecessaryauthenticationNow,wewillcomposeandsendanHTTPrequesttoretrievethefirstpageofthemessageswithoutauthenticationcredentials:

httpPOST':5000/api/messages/?page=1'

Thefollowingistheequivalentcurlcommand:

curl-iXGET':5000/api/messages/?page=1'

Wewillreceivea401Unauthorizedstatuscodeintheresponseheader.Thefollowinglinesshowasampleresponse:

HTTP/1.0401UNAUTHORIZED

Content-Length:19

Content-Type:text/html;charset=utf-8

Date:Mon,15Aug201601:16:36GMT

Server:Werkzeug/0.11.10Python/3.5.1

WWW-Authenticate:Basicrealm="AuthenticationRequired"

Ifwewanttoretrievemessages,thatis,tomakeaGETrequestto/api/messages/,weneedtoprovideauthenticationcredentialsusingHTTPauthentication.However,beforewecandothis,itisnecessarytocreateanewuser.Wewillusethenewusertotestournewresourceclassesrelatedtousersandourchangesinthepermissionspolicies.

httpPOST:5000/api/users/name='brandon'password='brandonpassword'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"brandon",

"password":"brandonpassword"}':5000/api/users/

Tip

Ofcourse,thecreationofauserandtheexecutionofthemethodsthatrequireauthenticationshouldonlybepossibleunderHTTPS.Thisway,theusernameandthepasswordwouldbeencrypted.

ThepreviouscommandwillcomposeandsendaPOSTHTTPrequestwiththespecifiedJSONkey-valuepairs.Therequestsspecify/api/user/,andtherefore,itwillmatchthe'/users/'URLroutefortheUserListresourceandruntheUserList.postmethodthatdoesn'trequireauthentication.Themethoddoesn'treceiveargumentsbecausetheURLroutedoesn'tincludeanyparameters.AstheHTTPverbfortherequestisPOST,Flaskcallsthepostmethod.

Thepreviouslyspecifiedpasswordonlyincludeslowercaseletters,andtherefore,itdoesn'tfulfilallthequalitativerequirementswehavespecifiedforthepasswordsinthe

Page 315: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

User.check_password_strength_and_hash_if_okmethod.Thus,Wewillreceivea400BadRequeststatuscodeintheresponseheaderandtheerrormessageindicatingtherequirementthatthepassworddidn'tfulfilintheJSONbody.Thefollowinglinesshowasampleresponse:

HTTP/1.0400BADREQUEST

Content-Length:75

Content-Type:application/json

Date:Mon,15Aug201601:29:55GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"error":"Thepasswordmustincludeatleastoneuppercaseletter"

}

Thefollowingcommandwillcreateauserwithavalidpassword:

httpPOST:5000/api/users/name='brandon'password='iA4!V3riS#c^R9'

Thefollowingistheequivalentcurlcommand:

curl-iXPOST-H"Content-Type:application/json"-d'{"name":"brandon",

"password":"iA4!V3riS#c^R9"}':5000/api/users/

IfthenewUserinstanceissuccessfullypersistedinthedatabase,thecallwillreturnanHTTP201CreatedstatuscodeandtherecentlypersistedUserserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequests,withthenewUserobjectintheJSONresponses.NotethattheresponseincludestheURL,url,forthecreateduseranddoesn'tincludeanyinformationrelatedtothepassword.

HTTP/1.0201CREATED

Content-Length:87

Content-Type:application/json

Date:Mon,15Aug201601:33:23GMT

Server:Werkzeug/0.11.10Python/3.5.1

{

"id":1,

"name":"brandon",

"url":"http://localhost:5000/api/users/1"

}

WecanrunthepreviouslyexplainedcommandtocheckthecontentsoftheusertablethatthemigrationscreatedinthePostgreSQLdatabase.Wewillnoticethatthehashed_passwordfieldcontentsarehashedforthenewrowintheusertable.ThefollowingscreenshotshowsthecontentsforthenewrowoftheusertableinaPostgreSQLdatabaseafterrunningtheHTTPrequest:

Page 316: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Ifwewanttoretrievethefirstpageofmessages,thatis,tomakeaGETrequestto/api/messages/,weneedtoprovideauthenticationcredentialsusingHTTPauthentication.Now,wewillcomposeandsendanHTTPrequesttoretrievethefirstpageofmessageswithauthenticationcredentials,thatis,withtheusernameandthepasswordwehaverecentlycreated:

http-a'brandon':'iA4!V3riS#c^R9'':5000/api/messages/?page=1'

Thefollowingistheequivalentcurlcommand:

curl--user'brandon':'iA4!V3riS#c^R9'-iXGET':5000/api/messages/?page=1'

Theuserwillbesuccessfullyauthenticatedandwewillbeabletoprocesstherequesttoretrievethefirstpageofmessages.WithallthechangeswehavemadetoourAPI,unauthenticatedrequestscanonlycreateanewuser.

Page 317: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Theflask.gobjectis:

1. Aproxythatprovidesaccesstothecurrentrequest.2. Aninstanceoftheflask_httpauth.HTTPBasicAuthclass.3. Aproxythatallowsustostoreonthiswhateverwewanttoshareforonerequest

only.

2. Thepasslibpackageprovides:1. Apasswordhashingframeworkthatsupportsmorethan30schemes.2. Anauthenticationframeworkthatautomaticallyaddsmodelsforusersand

permissiostoaFlaskapplication.3. AlightweightwebframeworkthatreplacesFlask.

3. Theauth.verify_passworddecoratorappliedtoafunction:1. MakesthisfunctionbecomethecallbackthatFlask-HTTPAuthwillusetohashthe

passwordforaspecificuser.2. MakesthisfunctionbecomethecallbackthatSQLAlchmeywillusetoverifythe

passwordforaspecificuser.3. MakesthisfunctionbecomethecallbackthatFlask-HTTPAuthwillusetoverifythe

passwordforaspecificuser.

4. Whenyouassignalistthatincludesauth.login_requiredtothemethod_decoratorspropertyofanysubclassofflask_restful.Resource,consideringthatauthisaninstanceoftheflask_httpauth.HTTPBasicAuth():1. Allthemethodsdeclaredintheresourcewillhavetheauth.login_required

decoratorappliedtothem.2. Thepostmethoddeclaredintheresourcewillhaveauth.login_required

decoratorappliedtoit.3. Anyofthefollowingmethodsdeclaredintheresourcewillhave

auth.login_requireddecoratorappliedtothem:delete,patch,postandput.

5. Whichofthefollowinglinesretrievestheintegervalueforthe'page'argumentfromtherequestobject,consideringthatthecodewouldberunningwithinamethoddefinedinasubclassofflask_restful.Resourceclass?1. page_number=request.get_argument('page',1,type=int)2. page_number=request.args.get('page',1,type=int)3. page_number=request.arguments.get('page',1,type=int)

Page 318: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,weimprovedtheRESTfulAPIinmanyways.Weaddeduserfriendlyerrormessageswhenresourcesaren'tunique.WetestedhowtoupdatesingleormultiplefieldswiththePATCHmethodandwecreatedourowngenericpaginationclass.

Then,westartedworkingwithauthenticationandpermissions.Weaddedausermodelandweupdatedthedatabase.WemademanychangesinthedifferentpiecesofcodetoachieveaspecificsecuritygoalandwetookadvantageofFlask-HTTPAuthandpasslibtouseHTTPauthenticationinourAPI.

NowthatwehavebuiltanimprovedacomplexAPIthatusespaginationandauthentication,wewilluseadditionalabstractionsincludedintheframeworkandwewillcode,execute,andimproveunittest,whichiswhatwearegoingtodiscussinthenextchapter.

Page 319: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter8.TestingandDeployinganAPIwithFlaskInthischapter,wewillconfigure,write,andexecuteunittestsandlearnafewthingsrelatedtodeployment.Wewill:

SetupunittestsCreateadatabasefortestingWriteafirstroundofunittestsRununittestsandchecktestingcoverageImprovetestingcoverageUnderstandstrategiesfordeploymentsandscalability

Page 320: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupunittestsWewillusenose2tomakeiteasiertodiscoverandrununittests.Wewillmeasuretestcoverage,andtherefore,wewillinstallthenecessarypackagetoallowustoruncoveragewithnose2.First,wewillinstallthenose2andcov-corepackagesinourvirtualenvironment.Thecov-corepackagewillallowustomeasuretestcoveragewithnose2.Then,wewillcreateanewPostgreSQLdatabasethatwewillusefortesting.Finally,wewillcreatetheconfigurationfileforthetestingenvironment.

MakesureyouquittheFlask'sdevelopmentserver.RememberthatyoujustneedtopressCtrl+CintheterminalortheCommandPromptwindowinwhichitisrunning.Wejustneedtorunthefollowingcommandtoinstallthenose2package:

pipinstallnose2

Thelastlinesoftheoutputwillindicatethatthedjango-nosepackagehasbeensuccessfullyinstalled.

Collectingnose2

Collectingsix>=1.1(fromnose2)

Downloadingsix-1.10.0-py2.py3-none-any.whl

Installingcollectedpackages:six,nose2

Successfullyinstallednose2-0.6.5six-1.10.0

Wejustneedtorunthefollowingcommandtoinstallthecov-corepackagethatwillalsoinstallthecoveragedependency:

pipinstallcov-core

Thelastlinesfortheoutputwillindicatethatthedjango-nosepackagehasbeensuccessfullyinstalled:

Collectingcov-core

Collectingcoverage>=3.6(fromcov-core)

Installingcollectedpackages:coverage,cov-core

Successfullyinstalledcov-core-1.15.0coverage-4.2

Now,wewillcreatethePostgreSQLdatabasethatwewilluseasarepositoryforourtestingenvironment.YouwillhavetodownloadandinstallaPostgreSQLdatabase,incaseyouaren'talreadyrunningitonthetestingenvironmentonyourcomputerorinatestingserver.

Tip

RemembertomakesurethatthePostgreSQLbinfolderisincludedinthePATHenvironmentalvariable.Youshouldbeabletoexecutethepsqlcommand-lineutilityfromyourcurrentTerminalorCommandPrompt.

WewillusethePostgreSQLcommand-linetoolstocreateanewdatabasenamed

Page 321: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

test_messages.IncaseyoualreadyhaveaPostgreSQLdatabasewiththisname,makesurethatyouuseanothernameinallthecommandsandconfigurations.YoucanperformthesametaskwithanyPostgreSQLGUItool.IncaseyouaredevelopingonLinux,itisnecessarytorunthecommandsasthepostgresuser.RunthefollowingcommandinmacOSorWindowstocreateanewdatabasenamedtest_messages.Notethatthecommandwon'tgenerateanyoutput:

createdbtest_messages

InLinux,runthefollowingcommandtousethepostgresuser:

sudo-upostgrescreatedbtest_messages

Now,wewillusethepsqlcommand-linetooltorunsomeSQLstatementstograntprivilegesonthedatabasetoauser.Incaseyouareusingadifferentserverthanthedevelopmentserver,youwillhavetocreatetheuserbeforegrantingprivileges.InmacOSorWindows,runthefollowingcommandtolaunchpsql:

psql

InLinux,runthefollowingcommandtousethepostgresuser

sudo-upsql

Then,runthefollowingSQLstatementsandfinallyenter\qtoexitthepsqlcommand-linetool.Replaceuser_namewithyourdesiredusernametouseinthenewdatabaseandpasswordwithyourchosenpassword.WewillusetheusernameandpasswordintheFlasktestingconfiguration.Youdon'tneedtorunthestepsincaseyouarealreadyworkingwithaspecificuserinPostgreSQLandyouhavealreadygrantedprivilegestothedatabasefortheuser:

GRANTALLPRIVILEGESONDATABASEtest_messagesTOuser_name;

\q

Createanewtest_config.pyfilewithintheapifolder.ThefollowinglinesshowthecodethatdeclaresvariablesthatdeterminetheconfigurationforFlaskandSQLAlchemyforourtestingenvironment.TheSQL_ALCHEMY_DATABASE_URIvariablegeneratesaSQLAlchemyURIforthePostgreSQLdatabasethatwewillusetorunallthemigrationsbeforestartingtestsandwewilldropalltheelementsafterexecutingallthetests.MakesureyouspecifythedesiredtestdatabasenameinthevalueforDB_NAMEandthatyouconfiguretheuser,password,host,andportbasedonyourPostgreSQLconfigurationforthetestingenvironment.Incaseyoufollowedtheprevioussteps,usethesettingsspecifiedinthesesteps.Thecodefileforthesampleisincludedintherestful_python_chapter_08_01folder.

importos

basedir=os.path.abspath(os.path.dirname(__file__))

DEBUG=True

PORT=5000

Page 322: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HOST="127.0.0.1"

SQLALCHEMY_ECHO=False

SQLALCHEMY_TRACK_MODIFICATIONS=True

SQLALCHEMY_DATABASE_URI="postgresql://{DB_USER}:

{DB_PASS}@{DB_ADDR}/{DB_NAME}".format(DB_USER="user_name",DB_PASS="password",

DB_ADDR="127.0.0.1",DB_NAME="test_messages")

SQLALCHEMY_MIGRATE_REPO=os.path.join(basedir,'db_repository')

TESTING=True

SERVER_NAME='127.0.0.1:5000'

PAGINATION_PAGE_SIZE=5

PAGINATION_PAGE_ARGUMENT_NAME='page'

#DisableCSRFprotectioninthetestingconfiguration

WTF_CSRF_ENABLED=False

Aswedidwiththesimilartestfilewecreatedforourdevelopmentenvironment,wewillspecifythepreviouslycreatedmoduleasanargumenttoafunctionthatwillcreateaFlaskappthatwewillusefortesting.Thisway,wehaveonemodulethatspecifiesallthevaluesforthedifferentconfigurationvariablesforourtestingenvironmentandanothermodulethatcreatesaFlaskappforourtestingenvironment.Itisalsopossibletocreateaclasshierarchywithoneclassforeachenvironmentwewanttouse.However,inoursamplecase,itiseasiertocreateanewconfigurationfileforourtestingenvironment.

Page 323: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WritingafirstroundofunittestsNow,wewillwriteafirstroundofunittests.Specifically,wewillwriteunittestsrelatedtotheuserandmessagecategoryresources:UserResource,UserListResource,CategoryResource,andCategoryListResource.Createanewtestssub-folderwithintheapifolder.Then,createanewtest_views.pyfilewithinthenewapi/testssub-folder.Addthefollowinglines,thatdeclaremanyimportstatementsandthefirstmethodsfortheInitialTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_01folder:

fromappimportcreate_app

frombase64importb64encode

fromflaskimportcurrent_app,json,url_for

frommodelsimportdb,Category,Message,User

importstatus

fromunittestimportTestCase

classInitialTests(TestCase):

defsetUp(self):

self.app=create_app('test_config')

self.test_client=self.app.test_client()

self.app_context=self.app.app_context()

self.app_context.push()

self.test_user_name='testuser'

self.test_user_password='T3s!p4s5w0RDd12#'

db.create_all()

deftearDown(self):

db.session.remove()

db.drop_all()

self.app_context.pop()

defget_accept_content_type_headers(self):

return{

'Accept':'application/json',

'Content-Type':'application/json'

}

defget_authentication_headers(self,username,password):

authentication_headers=self.get_accept_content_type_headers()

authentication_headers['Authorization']=\

'Basic'+b64encode((username+':'+password).encode('utf-

8')).decode('utf-8')

returnauthentication_headers

TheInitialTestsclassisasubclassofunittest.TestCase.TheclassoverridesthesetUpmethodthatwillbeexecutedbeforeeachtestmethodruns.Themethodcallsthecreate_appfunction,declaredintheappmodule,with'test_config'asanargument.ThefunctionwillsetupaFlaskappwiththismoduleastheconfigurationfile,andtherefore,theappwillusethepreviouslycreatedconfigurationfilethatspecifiesthedesiredvaluesforourtestingdatabaseandenvironment.Then,thecodesetsthetestingattributefortherecentlycreatedapp

Page 324: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

toTrueinorderfortheexceptiontopropagatetothetestclient.

Thenextlinecallstheself.app.test_clientmethodtocreateatestclientforthepreviouslycreatedFlaskapplicationandsavesthetestclientinthetest_clientattribute.WewillusethetestclientinourtestmethodstoeasilycomposeandsendrequeststoourAPI.Then,thecodesavesandpushestheapplicationcontextandcreatestwoattributeswiththeusernameandpasswordwewilluseforourtests.Finally,themethodcallsthedb.create_allmethodtocreateallthenecessarytablesinourtestdatabaseconfiguredinthetest_config.pyfile.

TheInitialTestsclassoverridesthetearDownmethodthatwillbeexecutedaftereachtestmethodruns.ThecoderemovestheSQLAlchemysession,dropsallthetablesthatwecreatedinthetestdatabasebeforestartingtheexecutionofthetests,andpopstheapplicationcontext.Thisway,aftereachtestfinishesitsexecution,thetestdatabasewillbeemptyagain.

Theget_accept_content_type_headersmethodbuildsandreturnsadictionary(dict)withthevaluesoftheAcceptandContent-Typeheaderkeyssetto'application/json'.Wewillcallthismethodinourtestswheneverwehavetobuildaheadertocomposeourrequestswithoutauthentication.

Theget_authentication_headersmethodcallsthepreviouslyexplainedget_accept_content_type_headersmethodtogeneratetheheaderkey-valuepairswithoutauthentication.Then,thecodeaddsthenecessaryvaluetotheAuthorizationkeywiththeappropriateencodingtoprovidetheusernameandpasswordreceivedintheusernameandpasswordarguments.Thelastlinereturnsthegenerateddictionarythatincludesauthenticationinformation.Wewillcallthismethodinourtestswheneverwehavetobuildaheadertocomposeourrequestswithauthentication.WewillusetheusernameandpasswordwestoredinattributesthesetUpmethod.

Openthepreviouslycreatedtest_views.pyfilewithinthenewapi/testssub-folder.AddthefollowinglinesthatdeclaremanymethodsfortheInitialTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_01folder.

deftest_request_without_authentication(self):

"""

Ensurewecannotaccessaresourcethatrequirestauthenticationwithout

anappropriateauthenticationheader

"""

response=self.test_client.get(

url_for('api.messagelistresource',_external=True),

headers=self.get_accept_content_type_headers())

self.assertTrue(response.status_code==status.HTTP_401_UNAUTHORIZED)

defcreate_user(self,name,password):

url=url_for('api.userlistresource',_external=True)

data={'name':name,'password':password}

response=self.test_client.post(

url,

headers=self.get_accept_content_type_headers(),

data=json.dumps(data))

Page 325: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

returnresponse

defcreate_category(self,name):

url=url_for('api.categorylistresource',_external=True)

data={'name':name}

response=self.test_client.post(

url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password),

data=json.dumps(data))

returnresponse

deftest_create_and_retrieve_category(self):

"""

EnsurewecancreateanewCategoryandthenretrieveit

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_category_name='NewInformation'

post_response=self.create_category(new_category_name)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Category.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

self.assertEqual(post_response_data['name'],new_category_name)

new_category_url=post_response_data['url']

get_response=self.test_client.get(

new_category_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['name'],new_category_name)

Thetest_request_without_authenticationmethodtestswhetherwehavebeenrejectedaccesstoaresourcethatrequiresauthenticationwhenwedon'tprovideanappropriateauthenticationheaderwiththerequest.ThemethodusesthetestclienttocomposeandsendanHTTPGETrequesttotheURLgeneratedforthe'api.messagelistresource'resourcetoretrievethelistofmessages.Weneedanauthenticatedrequesttoretrievethelistofmessages.However,thecodecallstheget_authentication_headersmethodtosetthevaluefortheheadersargumentinthecalltoself.test_client.get,andtherefore,thecodegeneratesarequestwithoutauthentication.Finally,themethodusesassertTruetocheckthatthestatus_codefortheresponseisHTTP401Unauthorized(status.HTTP_401_UNAUTHORIZED).

Thecreate_usermethodusesthetestclienttocomposeandsendanHTTPPOSTrequesttotheURLgeneratedforthe'api.userlistresource'resourcetocreateanewuserwiththenameandpasswordreceivedasarguments.Wedon'tneedanauthenticatedrequesttocreateanewuser,andtherefore,thecodecallsthepreviouslyexplainedget_accept_content_type_headersmethodtosetthevaluefortheheadersargumentinthecalltoself.test_client.post.Finally,thecodereturnstheresponsefromthePOSTrequest.Wheneverwehavetocreateanauthenticatedrequest,wewillcallthecreate_usermethodto

Page 326: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

createanewuser.

Thecreate_categorymethodusesthetestclienttocomposeandsendanHTTPPOSTrequesttotheURLgeneratedforthe'api.categorylistresource'resourcetocreateanewCategorywiththenamereceivedasanargument.WeneedanauthenticatedrequesttocreateanewCategory,andtherefore,thecodecallsthepreviouslyexplainedget_authentication_headersmethodtosetthevaluefortheheadersargumentinthecalltoself.test_client.post.Theusernameandpasswordaresettoself.test_user_nameandself.test_user_password.Finally,thecodereturnstheresponsefromthePOSTrequest.Wheneverwehavetocreateacategory,wewillcallthecreate_categorymethodaftertheappropriateuserthatauthenticatestherequesthasbeencreated.

Thetest_create_and_retrieve_categorymethodtestswhetherwecancreateanewCategoryandthenretrieveit.Themethodcallsthepreviouslyexplainedcreate_usermethodtocreateanewuserandthenuseittoauthenticatetheHTTPPOSTrequestgeneratedinthecreate_game_categorymethod.Then,thecodecomposesandsendsanHTTPGETmethodtoretrievetherecentlycreatedCategorywiththeURLreceivedintheresponseofthepreviousHTTPPOSTrequest.ThemethodusesassertEqualtocheckforthefollowingexpectedresults:

Thestatus_codefortheHTTPPOSTresponseisHTTP201Created(status.HTTP_201_CREATED)ThetotalnumberofCategoryobjectsretrievedfromthedatabaseis1Thestatus_codefortheHTTPGETresponseisHTTP200OK(status.HTTP_200_OK)ThevalueforthenamekeyintheHTTPGETresponseisequaltothenamespecifiedforthenewcategory

Openthepreviouslycreatedtest_views.pyfilewithinthenewapi/testssub-folder.AddthefollowinglinesthatdeclaremanymethodsfortheInitialTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_01folder.

deftest_create_duplicated_category(self):

"""

EnsurewecannotcreateaduplicatedCategory

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_category_name='NewInformation'

post_response=self.create_category(new_category_name)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Category.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

self.assertEqual(post_response_data['name'],new_category_name)

second_post_response=self.create_category(new_category_name)

self.assertEqual(second_post_response.status_code,

status.HTTP_400_BAD_REQUEST)

self.assertEqual(Category.query.count(),1)

Page 327: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

deftest_retrieve_categories_list(self):

"""

Ensurewecanretrievethecategorieslist

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_category_name_1='Error'

post_response_1=self.create_category(new_category_name_1)

self.assertEqual(post_response_1.status_code,status.HTTP_201_CREATED)

new_category_name_2='Warning'

post_response_2=self.create_category(new_category_name_2)

self.assertEqual(post_response_2.status_code,status.HTTP_201_CREATED)

url=url_for('api.categorylistresource',_external=True)

get_response=self.test_client.get(

url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(len(get_response_data),2)

self.assertEqual(get_response_data[0]['name'],new_category_name_1)

self.assertEqual(get_response_data[1]['name'],new_category_name_2)

"""

Ensurewecanupdatethenameforanexistingcategory

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_category_name_1='Error1'

post_response_1=self.create_category(new_category_name_1)

self.assertEqual(post_response_1.status_code,status.HTTP_201_CREATED)

post_response_data_1=json.loads(post_response_1.get_data(as_text=True))

new_category_url=post_response_data_1['url']

new_category_name_2='Error2'

data={'name':new_category_name_2}

patch_response=self.test_client.patch(

new_category_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password),

data=json.dumps(data))

self.assertEqual(patch_response.status_code,status.HTTP_200_OK)

get_response=self.test_client.get(

new_category_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['name'],new_category_name_2)

Theclassdeclaresthefollowingmethodswhosenamestartwiththetest_prefix:

Page 328: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

test_create_duplicated_category:Testswhethertheuniqueconstraintsdon'tmakeitpossibleforustocreatetwocategorieswiththesamenameornot.ThesecondtimewecomposeandsendanHTTPPOSTrequestwithaduplicatecategoryname,wemustreceiveanHTTP400BadRequeststatuscode(status.HTTP_400_BAD_REQUEST)andthetotalnumberofCategoryobjectsretrievedfromthedatabasemustbe1.test_retrieve_categories_list:Testswhetherwecanretrievethecategorieslistornot.First,themethodcreatestwocategoriesandthenitmakessurethattheretrievedlistincludesthetwocreatedcategories.test_update_game_category:Testswhetherwecanupdateasinglefieldforacategory,specifically,itsnamefield.Thecodemakessurethatthenamehasbeenupdated.

Tip

Notethateachtestthatrequiresaspecificconditioninthedatabasemustexecuteallthenecessarycodeforthedatabasetobeinthisspecificcondition.Forexample,inordertoupdateanexistingcategory,firstwemustcreateanewcategoryandthenwecanupdateit.Eachtestmethodwillbeexecutedwithoutdatafromthepreviouslyexecutedtestmethodsinthedatabase,thatis,eachtestwillrunwithadatabasecleanedofdatafromprevioustests.

Page 329: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Runningunittestswithnose2andcheckingtestingcoverageNow,runthefollowingcommandtocreateallthenecessarytablesinourtestdatabaseandusethenose2testrunningtoexecuteallthetestswecreated.ThetestrunnerwillexecuteallthemethodsforourInitialTestsclassthatstartwiththetest_prefixandwilldisplaytheresults.

Tip

Thetestswon'tmakechangestothedatabasewehavebeenusingwhenworkingontheAPI.Rememberthatweconfiguredthetest_messagesdatabaseasourtestdatabase.

Removetheapi.pyfilewecreatedinthepreviouschapterfromtheapifolderbecausewedon'twantthetestscoveragetotakeintoaccountthisfile.Gototheapifolderandrunthefollowingcommandwithinthesamevirtualenvironmentthatwehavebeenusing.Wewillusethe-voptiontoinstructnose2toprinttestcasenamesandstatuses.The--with-coverageoptionturnsontestcoveragereportinggeneration:

nose2-v--with-coverage

Thefollowinglinesshowthesampleoutput.

test_create_and_retrieve_category(test_views.InitialTests)...ok

test_create_duplicated_category(test_views.InitialTests)...ok

test_request_without_authentication(test_views.InitialTests)...ok

test_retrieve_categories_list(test_views.InitialTests)...ok

test_update_category(test_views.InitialTests)...ok

--------------------------------------------------------

Ran5testsin3.973s

OK

-----------coverage:platformwin32,python3.5.2-final-0--

NameStmtsMissCover

-----------------------------------------

app.py90100%

config.py11110%

helpers.py231822%

migrate.py990%

models.py1012773%

run.py440%

status.py56591%

test_config.py120100%

tests\test_views.py960100%

views.py20410947%

-----------------------------------------

TOTAL52518365%

Bydefault,nose2looksformoduleswhosenamesstartwiththetestprefix.Inthiscase,theonlymodulethatmatchesthecriteriaisthetest_viewsmodule.Inthemodulesthatmatchthe

Page 330: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

criteria,nose2loadstestsfromallthesubclassesofunittest.TestCaseandthefunctionswhosenamesstartwiththetestprefix.

Theoutputprovidesdetailsindicatingthatthetestrunnerdiscoveredandexecutedfivetestsandallofthempassed.TheoutputdisplaysthemethodnameandtheclassnameforeachmethodintheInitialTestsclassthatstartedwiththetest_prefixandrepresentedatesttobeexecuted.

ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageusesthecodeanalysistoolsandthetracinghooksincludedinthePythonstandardlibrarytodeterminewhichlinesofcodeareexecutableandhavebeenexecuted.Thereportprovidesatablewiththefollowingcolumns:

Name:ThePythonmodulename.Stmts:ThecountofexecutablestatementsforthePythonmodule.Miss:Thenumberofexecutablestatementsmissed,thatis,theonesthatweren'texecuted.Cover:Thecoverageofexecutablestatementsexpressedasapercentage.

Wedefinitelyhaveaverylowcoverageforviews.pyandhelpers.pybasedonthemeasurementsshowninthereport.Infact,wejustwroteafewtestsrelatedtocategoriesandusers,andtherefore,itmakessensethatthecoverageisreallylowfortheviews.Wedidn'tcreatetestsrelatedtomessages.

Wecanrunthecoveragecommandwiththe-mcommand-lineoptiontodisplaythelinenumbersofthemissingstatementsinanewMissingcolumn:

coveragereport-m

Thecommandwillusetheinformationfromthelastexecutionandwilldisplaythemissingstatements.Thenextlinesshowasampleoutputthatcorrespondstothepreviousexecutionoftheunittests:

NameStmtsMissCoverMissing

---------------------------------------------------

app.py90100%

config.py11110%7-20

helpers.py231822%13-19,23-44

migrate.py990%7-19

models.py1012773%28-29,44,46,48,50,52,54,

73-75,79-86,103,127-137

run.py440%7-14

status.py56591%2,6,10,14,18

test_config.py120100%

tests\test_views.py960100%

views.py20410947%43-45,51-58,63-64,67,71-72,

83-87,92-94,97-124,127-135,140-147,150-181,194-195,198,205-206,209-

212,215-223,235-236,239,250-253

---------------------------------------------------

TOTAL52518365%

Page 331: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,runthefollowingcommandtogetannotatedHTMLlistingsdetailingmissedlines:

coveragehtml

Opentheindex.htmlHTMLfilegeneratedinthehtmlcovfolderwithyourWebbrowser.ThefollowingpictureshowsanexamplereportthatcoveragegeneratedinHTMLformat:

Clickortapviews.pyandtheWebbrowserwillrenderaWebpagethatdisplaysthestatementsthatwererun,themissingonesandtheexcluded,withdifferentcolors.Wecanclickortapontherun,missingandexcludedbuttonstoshoworhidethebackgroundcolorthatrepresentsthestatusforeachlineofcode.Bydefault,themissinglinesofcodewillbedisplayedwithapinkbackground.Thus,wemustwriteunitteststhattargettheselinesofcodetoimproveourtestcoverage:

Page 332: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select
Page 333: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ImprovingtestingcoverageNow,wewillwriteadditionalunitteststoimprovethetestingcoverage.Specifically,wewillwriteunittestsrelatedtomessagesandusers.Opentheexistingapi/tests/test_views.pyfileandinsertthefollowinglinesafterthelastline,withintheInitialTestsclass.WeneedanewimportstatementandwewilldeclarethenewPlayerTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_02folder:

defcreate_message(self,message,duration,category):

url=url_for('api.messagelistresource',_external=True)

data={'message':message,'duration':duration,'category':category}

response=self.test_client.post(

url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password),

data=json.dumps(data))

returnresponse

deftest_create_and_retrieve_message(self):

"""

Ensurewecancreateanewmessageandthenretrieveit

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_message_message='WelcometotheIoTworld'

new_message_category='Information'

post_response=self.create_message(new_message_message,15,

new_message_category)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Message.query.count(),1)

#Themessageshouldhavecreatedanewcatagory

self.assertEqual(Category.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

self.assertEqual(post_response_data['message'],new_message_message)

new_message_url=post_response_data['url']

get_response=self.test_client.get(

new_message_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['message'],new_message_message)

self.assertEqual(get_response_data['category']['name'],

new_message_category)

deftest_create_duplicated_message(self):

"""

EnsurewecannotcreateaduplicatedMessage

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

Page 334: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

status.HTTP_201_CREATED)

new_message_message='WelcometotheIoTworld'

new_message_category='Information'

post_response=self.create_message(new_message_message,15,

new_message_category)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Message.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

self.assertEqual(post_response_data['message'],new_message_message)

new_message_url=post_response_data['url']

get_response=self.test_client.get(

new_message_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['message'],new_message_message)

self.assertEqual(get_response_data['category']['name'],

new_message_category)

second_post_response=self.create_message(new_message_message,15,

new_message_category)

self.assertEqual(second_post_response.status_code,

status.HTTP_400_BAD_REQUEST)

self.assertEqual(Message.query.count(),1)

TheprecedingcodeaddsmanymethodstotheInitialTestsclass.Thecreate_messagemethodreceivesthedesiredmessage,duration,andcategory(categoryname)forthenewmessageasarguments.ThemethodbuildstheURLandthedatadictionarytocomposeandsendanHTTPPOSTmethod,createanewmessage,andreturntheresponsegeneratedbythisrequest.Manytestmethodswillcallthecreate_messagemethodtocreateamessageandthencomposeandsendotherHTTPrequeststotheAPI.

Theclassdeclaresthefollowingmethodswhosenamesstartwiththetest_prefix:

test_create_and_retrieve_message:TestswhetherwecancreateanewMessageandthenretrieveit.test_create_duplicated_message:Testswhethertheuniqueconstraintsdon'tmakeitpossibleforustocreatetwomessageswiththesamemessage.ThesecondtimewecomposeandsendanHTTPPOSTrequestwithaduplicatemessage,wemustreceiveanHTTP400BadRequeststatuscode(status.HTTP_400_BAD_REQUEST)andthetotalnumberofMessageobjectsretrievedfromthedatabasemustbe1.

Opentheexistingapi/tests/test_views.pyfileandinsertthefollowinglinesafterthelastline,withintheInitialTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_02folder:

deftest_retrieve_messages_list(self):

"""

Ensurewecanretrievethemessagespaginatedlist

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

Page 335: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_message_message_1='WelcometotheIoTworld'

new_message_category_1='Information'

post_response=self.create_message(new_message_message_1,15,

new_message_category_1)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Message.query.count(),1)

new_message_message_2='Initializationoftheboardfailed'

new_message_category_2='Error'

post_response=self.create_message(new_message_message_2,10,

new_message_category_2)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Message.query.count(),2)

get_first_page_url=url_for('api.messagelistresource',_external=True)

get_first_page_response=self.test_client.get(

get_first_page_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_first_page_response_data=

json.loads(get_first_page_response.get_data(as_text=True))

self.assertEqual(get_first_page_response.status_code,

status.HTTP_200_OK)

self.assertEqual(get_first_page_response_data['count'],2)

self.assertIsNone(get_first_page_response_data['previous'])

self.assertIsNone(get_first_page_response_data['next'])

self.assertIsNotNone(get_first_page_response_data['results'])

self.assertEqual(len(get_first_page_response_data['results']),2)

self.assertEqual(get_first_page_response_data['results'][0]['message'],

new_message_message_1)

self.assertEqual(get_first_page_response_data['results'][1]['message'],

new_message_message_2)

get_second_page_url=url_for('api.messagelistresource',page=2)

get_second_page_response=self.test_client.get(

get_second_page_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_second_page_response_data=

json.loads(get_second_page_response.get_data(as_text=True))

self.assertEqual(get_second_page_response.status_code,

status.HTTP_200_OK)

self.assertIsNotNone(get_second_page_response_data['previous'])

self.assertEqual(get_second_page_response_data['previous'],

url_for('api.messagelistresource',page=1))

self.assertIsNone(get_second_page_response_data['next'])

self.assertIsNotNone(get_second_page_response_data['results'])

self.assertEqual(len(get_second_page_response_data['results']),0)

Thepreviouscodeaddedatest_retrieve_messages_listmethodtotheInitialTestsclass.Thismethodtestswhetherwecanretrievethepaginatedmessageslist.First,themethodcreatestwomessagesandthenitmakessurethattheretrievedlistincludesthetwocreatedmessagesinthefirstpage.Inaddition,themethodmakessurethatthesecondpagedoesn'tincludeanymessageandthatthevalueforthepreviouspageincludestheURLforthefirstpage.

Page 336: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Opentheexistingapi/tests/test_views.pyfileandinsertthefollowinglinesafterthelastline,withintheInitialTestsclass.Thecodefileforthesampleisincludedintherestful_python_chapter_08_02folder:

deftest_update_message(self):

"""

Ensurewecanupdateasinglefieldforanexistingmessage

"""

create_user_response=self.create_user(self.test_user_name,

self.test_user_password)

self.assertEqual(create_user_response.status_code,

status.HTTP_201_CREATED)

new_message_message_1='WelcometotheIoTworld'

new_message_category_1='Information'

post_response=self.create_message(new_message_message_1,30,

new_message_category_1)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(Message.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

new_message_url=post_response_data['url']

new_printed_times=1

new_printed_once=True

data={'printed_times':new_printed_times,'printed_once':

new_printed_once}

patch_response=self.test_client.patch(

new_message_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password),

data=json.dumps(data))

self.assertEqual(patch_response.status_code,status.HTTP_200_OK)

get_response=self.test_client.get(

new_message_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['printed_times'],new_printed_times)

self.assertEqual(get_response_data['printed_once'],new_printed_once)

deftest_create_and_retrieve_user(self):

"""

EnsurewecancreateanewUserandthenretrieveit

"""

new_user_name=self.test_user_name

new_user_password=self.test_user_password

post_response=self.create_user(new_user_name,new_user_password)

self.assertEqual(post_response.status_code,status.HTTP_201_CREATED)

self.assertEqual(User.query.count(),1)

post_response_data=json.loads(post_response.get_data(as_text=True))

self.assertEqual(post_response_data['name'],new_user_name)

new_user_url=post_response_data['url']

get_response=self.test_client.get(

new_user_url,

headers=self.get_authentication_headers(self.test_user_name,

self.test_user_password))

get_response_data=json.loads(get_response.get_data(as_text=True))

Page 337: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

self.assertEqual(get_response.status_code,status.HTTP_200_OK)

self.assertEqual(get_response_data['name'],new_user_name)

ThepreviouscodeaddedthefollowingtwomethodstotheInitialTestsclass-test_update_message-testswhetherwecanupdatemorethanonefieldsforamessage,specifically,thevaluesfortheprinted_timesandprinted_oncefields.Thecodemakessurethatbothfieldshavebeenupdated.test_create_and_retrieve_user:TestswhetherwecancreateanewUserandthenretrieveit.

Wejustcodedafewtestsrelatedtomessagesandonetestrelatedtousersinordertoimprovetestcoverageandnoticetheimpactonthetestcoveragereport.

Now,runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing:

nose2-v--with-coverage

Thefollowinglinesshowthesampleoutput:

test_create_and_retrieve_category(test_views.InitialTests)...ok

test_create_and_retrieve_message(test_views.InitialTests)...ok

test_create_and_retrieve_user(test_views.InitialTests)...ok

test_create_duplicated_category(test_views.InitialTests)...ok

test_create_duplicated_message(test_views.InitialTests)...ok

test_request_without_authentication(test_views.InitialTests)...ok

test_retrieve_categories_list(test_views.InitialTests)...ok

test_retrieve_messages_list(test_views.InitialTests)...ok

test_update_category(test_views.InitialTests)...ok

test_update_message(test_views.InitialTests)...ok

------------------------------------------------------------------

Ran10testsin25.938s

OK

-----------coverage:platformwin32,python3.5.2-final-0-------

NameStmtsMissCover

-----------------------------------------

app.py90100%

config.py11110%

helpers.py23196%

migrate.py990%

models.py1011189%

run.py440%

status.py56591%

test_config.py160100%

tests\test_views.py2030100%

views.py2046668%

-----------------------------------------

TOTAL63610783%

Theoutputprovideddetailsindicatingthatthetestrunnerexecuted10testsandallofthempassed.ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageincreasedtheCoverpercentageoftheviews.pymodulefrom47%inthepreviousrunto68%.Inaddition,thepercentageofthehelpers.pymoduleincreasedfrom22%to96%becausewe

Page 338: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

wroteteststhatusedpagination.Thenewadditionaltestswewroteexecutedadditionalcodeindifferentmodules,andtherefore,thereisanimpactinthecoveragereport.

Tip

Wejustcreatedafewunitteststounderstandhowwecancodethem.However,ofcourse,itwouldbenecessarytowritemoreteststoprovideanappropriatecoverageofallthefeaturedandexecutionscenariosincludedintheAPI.

Page 339: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingstrategiesfordeploymentsandscalabilityFlaskisalightweightmicroframeworkfortheWeb.However,ashappenswithDjango,oneofthebiggestdrawbacksrelatedtoFlaskandFlask-RESTfulisthateachHTTPrequestisblocking.Thus,whenevertheFlaskserverreceivesanHTTPrequest,itdoesn'tstartworkingonanyotherHTTPrequestsintheincomingqueueuntiltheserversendstheresponseforthefirstHTTPrequestitreceived.

WeusedFlasktodevelopaRESTfulWebService.TheykeyadvantageofthesekindofWebServicesisthattheyarestateless,thatis,theyshouldn'tkeepaclientstateonanyserver.OurAPIisagoodexampleofastatelessRESTfulWebServicewithFlaskandFlaskRESTful.Thus,wecanmaketheAPIrunonasmanyserversasnecessarytoachieveourscalabilitygoals.Obviously,wemusttakeintoaccountthatwecaneasilytransformthedatabaseserverinourscalabilitybottleneck.

Tip

Nowadays,wehaveahugenumberofcloud-basedalternativestodeployaRESTfulWebServicethatusesFlaskandFlask-RESTfulandmakeitextremelyscalable.

WealwayshavetomakesurethatweprofiletheAPIandthedatabasebeforewedeploythefirstversionofourAPI.Itisveryimportanttomakesurethatthegeneratedqueriesrunproperlyontheunderlyingdatabaseandthatthemostpopularqueriesdonotendupinsequentialscans.Itisusuallynecessarytoaddtheappropriateindexestothetablesinthedatabase.

WehavebeenusingbasicHTTPauthentication.Wecanimproveitwithatoken-basedauthentication.WemustmakesurethattheAPIrunsunderHTTPSinproductionenvironments.Inaddition,wemustmakesurethatwechangethefollowinglineintheapi/config.pyfile:

DEBUG=True

Wemustalwaysturnoffdebugmodeinproduction,andtherefore,wemustreplacethepreviouslinewiththefollowingone:

DEBUG=False

Tip

Itisconvenienttouseadifferentconfigurationfileforproduction.However,anotherapproachthatisbecomingextremelypopular,especiallyforcloud-nativeapplications,istostoreconfigurationintheenvironment.Ifwewanttodeploycloud-nativeRESTfulWebServicesandfollowtheguidelinesestablishedinthetwelve-factorApp,weshouldstore

Page 340: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

configintheenvironment.

Eachplatformincludesdetailedinstructionstodeployourapplication.Allofthemwillrequireustogeneratetherequirements.txtfilethatliststheapplicationdependenciestogetherwiththeirversions.Thisway,theplatformswillbeabletoinstallallthenecessarydependencieslistedinthefile.

Runthefollowingpipfreezetogeneratetherequirements.txtfile.

pipfreeze>requirements.txt

Thefollowinglinesshowthecontentsofasamplegeneratedrequirements.txtfile.However,bearinmindthatmanypackagesincreasetheirversionnumberquicklyandyoumightseedifferentversionsinyourconfiguration:

alembic==0.8.8

aniso8601==1.1.0

click==6.6

cov-core==1.15.0

coverage==4.2

Flask==0.11.1

Flask-HTTPAuth==3.2.1

flask-marshmallow==0.7.0

Flask-Migrate==2.0.0

Flask-RESTful==0.3.5

Flask-Script==2.0.5

Flask-SQLAlchemy==2.1

itsdangerous==0.24

Jinja2==2.8

Mako==1.0.4

MarkupSafe==0.23

marshmallow==2.10.2

marshmallow-sqlalchemy==0.10.0

nose2==0.6.5

passlib==1.6.5

psycopg2==2.6.2

python-dateutil==2.5.3

python-editor==1.0.1

pytz==2016.6.1

six==1.10.0

SQLAlchemy==1.0.15

Werkzeug==0.11.11

Page 341: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Bydefault,nose2looksformoduleswhosenamesstartwiththefollowingprefix:

1. test2. run3. unittest

2. Bydefault,nose2loadstestsfromallthesubclassesofthefollowingclass:1. unittest.Test2. unittest.TestCase3. unittest.RunTest

3. ThesetUpmethodinasubclassofunittest.TestCase:1. Isexecutedbeforeeachtestmethodruns.2. Isexecutedonlyoncebeforeallthetestsstarttheirexecution.3. Isexecutedonlyonceafterallthetestsfinishtheirexecution.

4. ThetearDownmethodinasubclassofunittest.TestCase:1. Isexecutedaftereachtestmethodruns.2. Isexecutedbeforeeachtestmethodruns.3. Isexecutedafteratestmethodonlywhenitfails.

5. Ifwedeclareaget_accept_content_type_headersmethodwithinasubclassofunittest.TestCase,bydefault,nose2:1. Willloadthismethodasatest.2. WillloadthismethodasthesetUpmethodforeachtest.3. Won'tloadthismethodasatest.

Page 342: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wesetupatestingenvironment.Weinstallednose2tomakeiteasytodiscoverandexecuteunittests,andwecreatedanewdatabasetobeusedfortesting.Wewroteafirstroundofunittests,measuredtestcoverage,andthenwewroteadditionalunitteststoimprovetestcoverage.Finally,weunderstoodmanyconsiderationsfordeploymentandscalability.

NowthatwehavebuiltacomplexAPIwithFlaskcombinedwithFlaskRESTful,andwetestedit,wewillmovetoanotherpopularPythonWebframework,Tornado,whichiswhatwearegoingtodiscussinthenextchapter.

Page 343: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter9.DevelopingRESTfulAPIswithTornadoInthischapter,wewillworkwithTornadotocreateaRESTfulWebAPIandstartworkingwiththislightweightWebframework.Wewillcoverthefollowingtopics:

DesigningaRESTfulAPItointeractwithslowsensorsandactuatorsUnderstandingthetasksperformedbyeachHTTPmethodSettingupavirtualenvironmentwithTornadoDeclaringstatuscodesfortheresponsesCreatingtheclassesthatrepresentadroneWritingrequesthandlersMappingURLpatternstorequesthandlersMakingHTTPrequeststotheTornadoAPIWorkingwithcommand-linetools-curlandHTTPieWorkingwithGUItools-Postmanandothers

Page 344: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DesigningaRESTfulAPItointeractwithslowsensorsandactuatorsImaginethatwehavetocreateaRESTfulAPItocontroladrone,alsoknownasanUnmannedAerialVehicle(UAV).ThedroneisanIoTdevicethatinteractswithmanysensorsandactuators,includingdigitalelectronicspeedcontrollerslinkedtoengines,propellers,andservomotors.

TheIoTdevicehaslimitedresources,andtherefore,wehavetousealightweightWebframework.OurAPIdoesn'tneedtointeractwithadatabase.Wedon'tneedaheavyweightWebframeworklikeDjango,andwewanttobeabletoprocessmanyrequestswithoutblockingtheWebserver.WeneedtheWebservertoprovideuswithgoodscalabilitywhileconsuminglimitedresources.Thus,ourchoiceistouseTornado,theopensourceversionofFriendFeed'sWebserver.

TheIoTdeviceiscapableofrunningPython3.5,Tornado,andotherPythonpackages.TornadoisaPythonWebframeworkandanasynchronousnetworkinglibrarythatprovidesexcellentscalabilityduetoitsnon-blockingnetworkI/O.Inaddition,TornadowillallowustoeasilyandquicklybuildalightweightRESTfulAPI.

WehavechosenTornadobecauseitismorelightweightthanDjangoanditmakesiteasyforustocreateanAPIthattakesadvantageofthenon-blockingnetworkI/O.Wedon'tneedtouseanORM,andwewanttostartrunningtheRESTfulAPIontheIoTdeviceassoonaspossibletoallowalltheteamstointeractwithit.

WewillinteractwithalibrarythatallowsustoruntheslowI/OoperationsthatinteractwiththesensorsandactuatorswithanexecutionthathappensoutsidetheGlobalInterpreterLock(GIL).Thus,wewillbeabletotakeadvantageofthenon-blockingfeatureinTornadowhenarequestneedstoexecuteanyoftheseslowI/Ooperations.InourfirstversionoftheAPI,wewillworkwithasynchronousexecution,andtherefore,whenanHTTPrequesttoourAPIrequiresrunningaslowI/Ooperation,wewillblocktherequestprocessingqueueuntiltheslowI/Ooperationwitheitherasensororanactuatorprovidesaresponse.WewillexecutetheI/OoperationwithasynchronousexecutionandTornadowon'tbeabletocontinueprocessingotherincomingHTTPrequestsuntilaresponseissenttotheHTTPrequest.

Then,wewillcreateasecondversionofourAPIthatwilltakeadvantageofthenon-blockingfeaturesincludedinTornado,incombinationwithasynchronousoperations.Inthesecondversion,whenanHTTPrequesttoourAPIrequiresrunningaslowI/Ooperation,wewon'tblocktherequestprocessingqueueuntiltheslowI/Ooperationwitheitherasensororanactuatorprovidesaresponse.WewillexecutetheI/Ooperationwithanasynchronousexecution,andTornadowillbeabletocontinueprocessingotherincomingHTTPrequests.

Tip

Page 345: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Wewillkeepourexamplesimpleandwewon'tusealibrarytointeractwithsensorsandactuators.Wewilljustprintinformationabouttheoperationsthatwillbeperformedbythesesensorsandactuators.However,inoursecondversionoftheAPI,wewillwriteourcodetomakeasynchronouscallsinordertounderstandtheadvantagesofthenon-blockingfeaturesinTornado.Wewilluseasimplifiedsetofsensorsandactuators—bearinmindthatdronesusuallyhavemoresensorsandactuators.OurgoalistolearnhowtoworkwithTornadotobuildaRESTfulAPI;wedon'twanttobecomeexpertsinbuildingdrones.

EachofthefollowingsensorsandactuatorswillbearesourceinourRESTfulAPI:

Ahexacopter,thatis,a6-rotorhelicopterAnaltimeter(altitudesensor)AblueLED(Light-EmittingDiode)AwhiteLED

ThefollowingtableshowstheHTTPverbs,thescope,andthesemanticsforthemethodsthatourfirstversionoftheAPImustsupport.EachmethodiscomposedbyanHTTPverbandascopeandallthemethodshaveawell-definedmeaningforallsensorsandactuators.InourAPI,eachsensororactuatorhasitsownuniqueURL:

HTTPverb Scope Semantics

GET Hexacopter Retrievethecurrenthexacopter'smotorspeedinRPMsanditsstatus(turnedonoroff)

PATCH Hexacopter Setthecurrenthexacopter'smotorspeedinRPMs

GET LED RetrievethebrightnesslevelforasingleLED

PATCH LED UpdatethebrightnesslevelforasingleLED

GET Altimeter Retrievethecurrentaltitudeinfeet

Page 346: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingthetasksperformedbyeachHTTPmethodLet'sconsiderthathttp://localhost:8888/hexacopters/1istheURLthatidentifiesthehexacopterforourdrone.

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(PATCH)andrequestURL(http://localhost:8888/hexacopters/1)tosetthehexacopter'smotorspeedinRPMsanditsstatus.Inaddition,wehavetoprovidetheJSONkey-valuepairswiththenecessaryfieldnameandthevaluetospecifythedesiredspeed.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefield,makesurethatitisavalidspeedandmakethenecessarycallstoadjustthespeedwithanasynchronousexecution.Afterthespeedforthehexacopterisset,theserverwillreturna200OKstatuscodeandaJSONbodywiththerecentlyupdatedhexacoptervaluesserializedtoJSON:

PATCHhttp://localhost:8888/hexacopters/1

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:8888/hexacopter/1)toretrievethecurrentvaluesforthehexacopter.Theserverwillmakethenecessarycallstoretrievethestatusandthespeedforthehexacopterwithanasynchronousexecution.Asaresultoftherequest,theserverwillreturna200OKstatuscodeandaJSONbodywiththeserializedkey-valuepairsthatspecifythestatusandspeedforthehexacopter.Ifanumberdifferentthan1isspecified,theserverwillreturnjusta404NotFoundstatus:

GEThttp://localhost:8888/hexacopters/1

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(PATCH)andrequestURL(http://localhost:8888/led/{id})tosetthebrightnesslevelforaspecificLEDwhoseidmatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Forexample,ifweusetherequestURLhttp://localhost:8888/led/1,theserverwillsetthebrightnesslevelfortheledwhoseidmatches1.Inaddition,wehavetoprovidetheJSONkey-valuepairswiththenecessaryfieldnameandthevaluetospecifythedesiredbrightnesslevel.Asaresultoftherequest,theserverwillvalidatetheprovidedvaluesforthefield,makesurethatitisavalidbrightnesslevelandmakethenecessarycallstoadjustthebrightnesslevelwithanasynchronousexecution.AfterthebrightnesslevelfortheLEDisset,theserverwillreturna200OKstatuscodeandaJSONbodywiththerecentlyupdatedLEDvaluesserializedtoJSON:

PATCHhttp://localhost:8888/led/{id}

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:8888/led/{id})toretrievethecurrentvaluesfortheLEDwhoseidmatchesthespecifiednumericvalueintheplacewhere{id}iswritten.Forexample,

Page 347: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ifweusetherequestURLhttp://localhost:8888/led/1,theserverwillretrievetheLEDwhoseidmatches1.TheserverwillmakethenecessarycallstoretrievethevaluesfortheLEDwithanasynchronousexecution.Asaresultoftherequest,theserverwillreturna200OKstatuscodeandaJSONbodywiththeserializedkey-valuepairsthatspecifythevaluesfortheLED.IfnoLEDmatchesthespecifiedid,theserverwillreturnjusta404NotFoundstatus:

GEThttp://localhost:8888/led/{id}

WehavetocomposeandsendanHTTPrequestwiththefollowingHTTPverb(GET)andrequestURL(http://localhost:8888/altimeter/1)toretrievethecurrentvaluesforthealtimeter.Theserverwillmakethenecessarycallstoretrievethevaluesforthealtimeterwithanasynchronousexecution.Asaresultoftherequest,theserverwillreturna200OKstatuscodeandaJSONbodywiththeserializedkey-valuepairsthatspecifythevaluesforthealtimeter.Ifanumberdifferentthan1isspecified,theserverwillreturnjusta404NotFoundstatus:

GEThttp://localhost:8888/altimeter/1

Page 348: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupavirtualenvironmentwithTornadoInChapter1,DevelopingRESTfulAPIswithDjango,welearnedthat,throughoutthisbook,weweregoingtoworkwiththelightweightvirtualenvironmentsintroducedinPython3.3andimprovedinPython3.4.Now,wewillfollowmanystepscreateanewlightweightvirtualenvironmenttoworkwithTornado.ItishighlyrecommendedtoreadChapter1,DevelopingRESTfulAPIswithDjango,incaseyoudon'thaveexperiencewithlightweightvirtualenvironmentsinPython.Thechapterincludesallthedetailedexplanationsabouttheeffectsofthestepswearegoingtofollow.

First,wehavetoselectthetargetfolderordirectoryforourvirtualenvironment.ThefollowingisthepathwewilluseintheexampleformacOSandLinux.ThetargetfolderforthevirtualenvironmentwillbethePythonREST/Tornado01folderwithinourhomedirectory.Forexample,ifourhomedirectoryinmacOSorLinuxis/Users/gaston,thevirtualenvironmentwillbecreatedwithin/Users/gaston/PythonREST/Tornado01.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand:

~/PythonREST/Tornado01

WewillusethefollowingpathintheexampleforWindows.ThetargetfolderforthevirtualenvironmentwillbethePythonREST\Tornado01folderwithinouruserprofilefolder.Forexample,ifouruserprofilefolderisC:\Users\Gaston,thevirtualenvironmentwillbecreatedwithinC:\Users\gaston\PythonREST\Tornado01.Youcanreplacethespecifiedpathwithyourdesiredpathineachcommand:

%USERPROFILE%\PythonREST\Tornado01

OpenaTerminalinmacOSorLinuxandexecutethefollowingcommandtocreateavirtualenvironment:

python3-mvenv~/PythonREST/Tornado01

InWindows,executethefollowingcommandtocreateavirtualenvironment:

python-mvenv%USERPROFILE%\PythonREST\Tornado01

Theprecedingcommanddoesn'tproduceanyoutput.Nowthatwehavecreatedavirtualenvironment,wewillrunaplatform-specificscripttoactivateit.Afterweactivatethevirtualenvironment,wewillinstallpackagesthatwillonlybeavailableinthisvirtualenvironment.

IfyourTerminalisconfiguredtousethebashshellinmacOSorLinux,runthefollowingcommandtoactivatethevirtualenvironment.Thecommandalsoworksforthezshshell:

source~/PythonREST/Torando01/bin/activate

IfyourTerminalisconfiguredtouseeitherthecshortcshshell,runthefollowingcommandtoactivatethevirtualenvironment:

Page 349: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

source~/PythonREST/Torando01/bin/activate.csh

IfyourTerminalisconfiguredtouseeitherthefishshell,runthefollowingcommandtoactivatethevirtualenvironment:

source~/PythonREST/Tornado01/bin/activate.fish

InWindows,youcanruneitherabatchfileintheCommandPromptoraWindowsPowerShellscripttoactivatethevirtualenvironment.Ifyoupreferthecommandprompt,runthefollowingcommandintheWindowscommandlinetoactivatethevirtualenvironment:

%USERPROFILE%\PythonREST\Tornado01\Scripts\activate.bat

IfyouprefertheWindowsPowerShell,launchitandrunthefollowingcommandstoactivatethevirtualenvironment.However,noticethatyoushouldhavescriptsexecutionenabledinWindowsPowerShelltobeabletorunthescript:

cd$env:USERPROFILE

PythonREST\Tornado01\Scripts\Activate.ps1

Afteryouactivatethevirtualenvironment,theCommandPromptwilldisplaythevirtualenvironmentrootfoldernameenclosedinparenthesesasaprefixofthedefaultprompttoremindusthatweareworkinginthevirtualenvironment.Inthiscase,wewillsee(Tornado01)asaprefixfortheCommandPromptbecausetherootfolderfortheactivatedvirtualenvironmentisTornado01.

Wehavecreatedandactivatedavirtualenvironment.ItistimetorunmanycommandsthatwillbethesameforeithermacOS,Linux,orWindows.Now,wemustrunthefollowingcommandtoinstallTornadowithpip:

pipinstalltornado

Thelastlinesfortheoutputwillindicateallthepackagesthathavebeensuccessfullyinstalled,includingtornado:

Collectingtornado

Downloadingtornado-4.4.1.tar.gz(456kB)

Installingcollectedpackages:tornado

Runningsetup.pyinstallfortornado

Successfullyinstalledtornado-4.4.1

Page 350: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

DeclaringstatuscodesfortheresponsesTornadoallowsustogenerateresponseswithanystatuscodethatisincludedinthehttp.HTTPStatusdictionary.Wemightusethisdictionarytoreturneasytounderstanddescriptionsasthestatuscodes,suchasHTTPStatus.OKandHTTPStatus.NOT_FOUNDafterimportingtheHTTPStatusdictionaryfromthehttpmodule.Thesenamesareeasytounderstandbuttheydon'tincludethestatuscodenumberintheirdescription.

Wehavebeenworkingwithmanydifferentframeworksandmicro-frameworksthroughoutthebook,andtherefore,wewillborrowthecodethatdeclaresveryusefulfunctionsandvariablesrelatedtoHTTPstatuscodesfromthestatus.pyfileincludedinDjangoRESTFramework,thatis,theframeworkwehavebeenusinginthefirstchapters.ThemainadvantageofusingthesevariablesfortheHTTPstatuscodesisthattheirnamesincludeboththenumberandthedescription.Whenwereadthecode,wewillunderstandthestatuscodenumberandtheirmeaning.Forexample,insteadofusingHTTPStatus.OK,wewillusestatus.HTTP_200_OK.

Createanewstatus.pyfilewithintherootfolderfortherecentlycreatedvirtualenvironment.ThefollowinglinesshowthecodethatdeclaresfunctionsandvariableswithdescriptiveHTTPstatuscodesinthestatus.pyfile,borrowedfromtherest_framework.statusmodule.Wedon'twanttoreinventthewheelandthemoduleprovideseverythingweneedtoworkwithHTTPstatuscodesinourTornado-basedAPI.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

defis_informational(code):

returncode>=100andcode<=199

defis_success(code):

returncode>=200andcode<=299

defis_redirect(code):

returncode>=300andcode<=399

defis_client_error(code):

returncode>=400andcode<=499

defis_server_error(code):

returncode>=500andcode<=599

HTTP_100_CONTINUE=100

HTTP_101_SWITCHING_PROTOCOLS=101

HTTP_200_OK=200

HTTP_201_CREATED=201

HTTP_202_ACCEPTED=202

Page 351: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTP_203_NON_AUTHORITATIVE_INFORMATION=203

HTTP_204_NO_CONTENT=204

HTTP_205_RESET_CONTENT=205

HTTP_206_PARTIAL_CONTENT=206

HTTP_300_MULTIPLE_CHOICES=300

HTTP_301_MOVED_PERMANENTLY=301

HTTP_302_FOUND=302

HTTP_303_SEE_OTHER=303

HTTP_304_NOT_MODIFIED=304

HTTP_305_USE_PROXY=305

HTTP_306_RESERVED=306

HTTP_307_TEMPORARY_REDIRECT=307

HTTP_400_BAD_REQUEST=400

HTTP_401_UNAUTHORIZED=401

HTTP_402_PAYMENT_REQUIRED=402

HTTP_403_FORBIDDEN=403

HTTP_404_NOT_FOUND=404

HTTP_405_METHOD_NOT_ALLOWED=405

HTTP_406_NOT_ACCEPTABLE=406

HTTP_407_PROXY_AUTHENTICATION_REQUIRED=407

HTTP_408_REQUEST_TIMEOUT=408

HTTP_409_CONFLICT=409

HTTP_410_GONE=410

HTTP_411_LENGTH_REQUIRED=411

HTTP_412_PRECONDITION_FAILED=412

HTTP_413_REQUEST_ENTITY_TOO_LARGE=413

HTTP_414_REQUEST_URI_TOO_LONG=414

HTTP_415_UNSUPPORTED_MEDIA_TYPE=415

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE=416

HTTP_417_EXPECTATION_FAILED=417

HTTP_428_PRECONDITION_REQUIRED=428

HTTP_429_TOO_MANY_REQUESTS=429

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE=431

HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS=451

HTTP_500_INTERNAL_SERVER_ERROR=500

HTTP_501_NOT_IMPLEMENTED=501

HTTP_502_BAD_GATEWAY=502

HTTP_503_SERVICE_UNAVAILABLE=503

HTTP_504_GATEWAY_TIMEOUT=504

HTTP_505_HTTP_VERSION_NOT_SUPPORTED=505

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED=511

ThecodedeclaresfivefunctionsthatreceivetheHTTPstatuscodeinthecodeargumentanddeterminetowhichofthefollowingcategoriesthestatuscodebelongsto:informational,success,redirect,andclienterrororservererrorcategories.Wewillusethepreviousvariableswhenwehavetoreturnaspecificstatuscode.Forexample,incasewehavetoreturna404NotFoundstatuscode,wewillreturnstatus.HTTP_404_NOT_FOUND,insteadofjust404orHTTPStatus.NOT_FOUND.

Page 352: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

CreatingtheclassesthatrepresentadroneWewillcreateasmanyclassesaswewillusetorepresentthedifferentcomponentsofadrone.Inareal-lifeexample,theseclasseswillinteractwithalibrarythatinteractswithsensorsandactuators.Inordertokeepourexamplesimple,wewillmakecallstotime.sleeptosimulateinteractionsthattakesometimetosetorgetvaluestoandfromsensorsandactuators.

First,wewillcreateaHexacopterclassthatwewillusetorepresentthehexacopterandaHexacopterStatusclassthatwewillusetostorestatusdataforthehexacopter.Createanewdrone.pyfile.ThefollowinglinesshowsallthenecessaryimportsfortheclassesthatwewillcreateandthecodethatdeclarestheHexacopterandHexacopterStatusclassesinthedrone.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

fromrandomimportrandint

fromtimeimportsleep

classHexacopterStatus:

def__init__(self,motor_speed,turned_on):

self.motor_speed=motor_speed

self.turned_on=turned_on

classHexacopter:

MIN_SPEED=0

MAX_SPEED=1000

def__init__(self):

self.motor_speed=self.__class__.MIN_SPEED

self.turned_on=False

defget_motor_speed(self):

returnself.motor_speed

defset_motor_speed(self,motor_speed):

ifmotor_speed<self.__class__.MIN_SPEED:

raiseValueError('Theminimumspeedis

{0}'.format(self.__class__.MIN_SPEED))

ifmotor_speed>self.__class__.MAX_SPEED:

raiseValueError('Themaximumspeedis

{0}'.format(self.__class__.MAX_SPEED))

self.motor_speed=motor_speed

self.turned_on=(self.motor_speedisnot0)

sleep(2)

returnHexacopterStatus(self.get_motor_speed(),self.is_turned_on())

defis_turned_on(self):

returnself.turned_on

defget_hexacopter_status(self):

Page 353: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

sleep(3)

returnHexacopterStatus(self.get_motor_speed(),self.is_turned_on())

TheHexacopterStatusclassjustdeclaresaconstructor,thatis,the__init__method.Thismethodreceivesmanyargumentsandusesthemtoinitializetheattributeswiththesamenames:motor_speedandturned_on.

TheHexacopterclassdeclarestwoclassattributesthatspecifytheminimumandmaximumspeedvalues:MIN_SPEEDandMAX_SPEED.Theconstructor,thatis,the__init__method,initializesthemotor_speedattributewiththeMIN_SPEEDvalueandsetstheturned_onattributetoFalse.

Theget_motor_speedmethodreturnsthevalueofthemotor_speedattribute.Theset_motor_speedmethodcheckswhetherthevalueforthemotor_speedargumentisinthevalidrange.Incasethevalidationfails,themethodraisesaValueErrorexception.Otherwise,themethodsetsthevalueofthemotor_speedattributewiththereceivedvalueandsetsthevaluefortheturned_onattributetoTrueifthemotor_speedisgreaterthan0.Finally,themethodcallssleeptosimulateittakestwosecondstoretrievethehexacopterstatusandthenreturnsaHexacopterStatusinstanceinitializedwiththemotor_speedandturned_onattributevalues,retrievedthroughspecificmethods.

Theget_hexacopter_statusmethodcallssleeptosimulateittakesthreesecondstoretrievethehexacopterstatusandthenreturnsaHexacopterStatusinstanceinitializedwiththemotor_speedandturned_onattributevalues.

Now,wewillcreateaLightEmittingDiodeclassthatwewillusetorepresenteachLED.Openthepreviouslycreateddrone.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

classLightEmittingDiode:

MIN_BRIGHTNESS_LEVEL=0

MAX_BRIGHTNESS_LEVEL=255

def__init__(self,identifier,description):

self.identifier=identifier

self.description=description

self.brightness_level=self.__class__.MIN_BRIGHTNESS_LEVEL

defget_brightness_level(self):

sleep(1)

returnself.brightness_level

defset_brightness_level(self,brightness_level):

ifbrightness_level<self.__class__.MIN_BRIGHTNESS_LEVEL:

raiseValueError('Theminimumbrightnesslevelis

{0}'.format(self.__class__.MIN_BRIGHTNESS_LEVEL))

ifbrightness_level>self.__class__.MAX_BRIGHTNESS_LEVEL:

raiseValueError('Themaximumbrightnesslevelis

{0}'.format(self.__class__.MAX_BRIGHTNESS_LEVEL))

sleep(2)

Page 354: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

self.brightness_level=brightness_level

TheLightEmittingDiodeclassdeclarestwoclassattributesthatspecifytheminimumandmaximumbrightnesslevelvalues:MIN_BRIGHTNESS_LEVELandMAX_BRIGHTNESS_LEVEL.Theconstructor,thatis,the__init__method,initializesthebrightness_levelattributewiththeMIN_BRIGHTNESS_LEVELandtheidanddescriptionattributeswiththevaluesreceivedintheargumentswiththesamenames.

Theget_brightness_levelmethodcallssleeptosimulate,ittakes1secondtoretrievethebrightnesslevelforthewiredLEDandthenreturnsthevalueofthebrightness_levelattribute.

Theset_brightness_levelmethodcheckswhetherthevalueforthebrightness_levelargumentisinthevalidrange.Incasethevalidationfails,themethodraisesaValueErrorexception.Otherwise,themethodcallssleeptosimulateittakestwosecondstosetthenewbrightnesslevelandfinallysetsthevalueofthebrightness_levelattributewiththereceivedvalue.

Now,wewillcreateanAltimeterclassthatwewillusetorepresentthealtimeter.Openthepreviouslycreateddrone.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

classAltimeter:

defget_altitude(self):

sleep(1)

returnrandint(0,3000)

TheAltimeterclassdeclaresaget_altitudemethodthatcallssleeptosimulateittakesonesecondtoretrievethealtitudefromthealtimeterandfinallygeneratesarandomintegerfrom0to3000(inclusive)andreturnsit.

Finally,wewillcreateaDroneclassthatwewillusetorepresentthedronewithitssensorsandactuators.Openthepreviouslycreateddrone.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder

classDrone:

def__init__(self):

self.hexacopter=Hexacopter()

self.altimeter=Altimeter()

self.blue_led=LightEmittingDiode(1,'BlueLED')

self.white_led=LightEmittingDiode(2,'WhiteLED')

self.leds={

self.blue_led.identifier:self.blue_led,

self.white_led.identifier:self.white_led

}

TheDroneclassjustdeclaresaconstructor,thatis,the__init__methodthatcreatesinstancesofthepreviouslydeclaredclassesthatrepresentthedifferentcomponentsforthedrone.Theledsattributesavesadictionarythathasakey-valuepairforeachLightEmittingDiode

Page 355: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

instancewithitsidanditsinstance.

Page 356: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WritingrequesthandlersThemainbuildingblocksforaRESTfulAPIintornadoaresubclassesofthetornado.web.RequestHandlerclass,thatis,thebaseclassforHTTPrequesthandlersinTornado.WejustneedtocreateasubclassofthisclassanddeclarethemethodsforeachsupportedHTTPverb.WehavetooverridethemethodstohandleHTTPrequests.Then,wehavetomaptheURLpatternstoeachsubclassoftornado.web.RequestHandlerinthetornado.web.ApplicationinstancethatrepresentstheTornadoWebapplication.

First,wewillcreateaHexacopterHandlerclassthatwewillusetohandlerequestsforthehexacopterresource.Createanewapi.pyfile.ThefollowinglinesshowallthenecessaryimportsfortheclassesthatwewillcreateandthecodethatdeclarestheHexacopterHandlerclassinthedrone.pyfile.Enterthenextlinesinthenewapi.pyfile.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

importstatus

fromdatetimeimportdate

fromtornadoimportweb,escape,ioloop,httpclient,gen

fromdroneimportAltimeter,Drone,Hexacopter,LightEmittingDiode

drone=Drone()

classHexacopterHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET","PATCH")

HEXACOPTER_ID=1

defget(self,id):

ifint(id)isnotself.__class__.HEXACOPTER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

return

print("I'vestartedretrievinghexacopter'sstatus")

hexacopter_status=drone.hexacopter.get_hexacopter_status()

print("I'vefinishedretrievinghexacopter'sstatus")

response={

'speed':hexacopter_status.motor_speed,

'turned_on':hexacopter_status.turned_on,

}

self.set_status(status.HTTP_200_OK)

self.write(response)

defpatch(self,id):

ifint(id)isnotself.__class__.HEXACOPTER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

return

request_data=escape.json_decode(self.request.body)

if('motor_speed'notinrequest_data.keys())or\

(request_data['motor_speed']isNone):

self.set_status(status.HTTP_400_BAD_REQUEST)

return

try:

Page 357: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

motor_speed=int(request_data['motor_speed'])

print("I'vestartedsettingthehexacopter'smotorspeed")

hexacopter_status=drone.hexacopter.set_motor_speed(motor_speed)

print("I'vefinishedsettingthehexacopter'smotorspeed")

response={

'speed':hexacopter_status.motor_speed,

'turned_on':hexacopter_status.turned_on,

}

self.set_status(status.HTTP_200_OK)

self.write(response)

exceptValueErrorase:

print("I'vefailedsettingthehexacopter'smotorspeed")

self.set_status(status.HTTP_400_BAD_REQUEST)

response={

'error':e.args[0]

}

self.write(response)

TheHexacopterHandlerclassisasubclassoftornado.web.RequestHandleranddeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestonthisHTTPhandler:

get:Thismethodreceivestheidofthehexacopterwhosestatushastoberetrievedintheidargument.Ifthereceivediddoesn'tmatchthevalueoftheHEXACOPTER_IDclassattribute,thecodecallstheself.set_statusmethodwithstatus.HTTP_404_NOT_FOUNDasanargumenttosetthestatuscodefortheresponsetoHTTP404NotFound.Otherwise,thecodeprintsamessageindicatingthatitstartedretrievingthehexacopter'sstatusandcallsthedrone.hexacopter.get_hexacopter_statusmethodwithasynchronousexecutionandsavestheresultinthehexacopter_statusvariable.Then,thecodewritesamessageindicatingitfinishedretrievingthestatusandgeneratesaresponsedictionarywiththe'speed'and'turned_on'keysandtheirvalues.Finally,thecodecallstheself.set_statusmethodwithstatus.HTTP_200_OKasanargumenttosetthestatuscodefortheresponsetoHTTP200OKandcallstheself.writemethodwiththeresponsedictionaryasanargument.Becauseresponseisadictionary,TornadoautomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Typeheadertoapplication/json.patch:Thismethodreceivestheidofthehexacopterthathastobeupdatedorpatchedintheidargument.Asithappenedinthepreviouslyexplainedgetmethod,thecodereturnsanHTTP404NotFoundincasethereceivediddoesn'tmatchthevalueoftheHEXACOPTER_IDclassattribute.Otherwise,thecodecallsthetornado.escape.json_decodemethodwithself.request.bodyasanargumenttogeneratePythonobjectsfortheJSONstringoftherequestbodyandsavesthegenerateddictionaryintherequest_datavariable.Ifthedictionarydoesn'tincludeakeynamed'motor_speed',thecodereturnsanHTTP400BadRequeststatuscode.Incasethereisakey,thecodeprintsamessageindicatingthatitstartedsettingthehexacopter'sspeed,callsthedrone.hexacopter.set_motor_speedmethodwithasynchronousexecutionandsavestheresultinthehexacopter_statusvariable.Ifthevaluespecifiedforthemotorspeedisnotvalid,aValueErrorexceptionwillbecaughtandthecodewillreturnan

Page 358: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

HTTP400BadRequeststatuscodeandthevalidationerrormessagesastheresponsebody.Otherwise,thecodewritesamessageindicatingitfinishedsettingthemotorspeedandgeneratesaresponsedictionarywiththe'speed'and'turned_on'keysandtheirvalues.Finally,thecodecallstheself.set_statusmethodwithstatus.HTTP_200_OKasanargumenttosetthestatuscodefortheresponsetoHTTP200OKandcallstheself.writemethodwiththeresponsedictionaryasanargument.Sinceresponseisadictionary,TornadoautomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Typeheadertoapplication/json.

TheclassoverridestheSUPPORTED_METHODSclassvariablewithatuplethatindicatestheclassjustsupportstheGETandPATCHmethods.Thisway,incasethehandlerisrequestedamethodthatisn'tincludedintheSUPPORTED_METHODStuple,theserverwillautomaticallyreturna405MethodNotAllowedstatuscode.

Now,wewillcreateaLedHandlerclassthatwewillusetorepresenttheLEDresources.Openthepreviouslycreatedapi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

classLedHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET","PATCH")

defget(self,id):

int_id=int(id)

ifint_idnotindrone.leds.keys():

self.set_status(status.HTTP_404_NOT_FOUND)

return

led=drone.leds[int_id]

print("I'vestartedretrieving{0}'sstatus".format(led.description))

brightness_level=led.get_brightness_level()

print("I'vefinishedretrieving{0}'sstatus".format(led.description))

response={

'id':led.identifier,

'description':led.description,

'brightness_level':brightness_level

}

self.set_status(status.HTTP_200_OK)

self.write(response)

defpatch(self,id):

int_id=int(id)

ifint_idnotindrone.leds.keys():

self.set_status(status.HTTP_404_NOT_FOUND)

return

led=drone.leds[int_id]

request_data=escape.json_decode(self.request.body)

if('brightness_level'notinrequest_data.keys())or\

(request_data['brightness_level']isNone):

self.set_status(status.HTTP_400_BAD_REQUEST)

return

try:

brightness_level=int(request_data['brightness_level'])

print("I'vestartedsettingthe{0}'sbrightness

Page 359: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

level".format(led.description))

led.set_brightness_level(brightness_level)

print("I'vefinishedsettingthe{0}'sbrightness

level".format(led.description))

response={

'id':led.identifier,

'description':led.description,

'brightness_level':brightness_level

}

self.set_status(status.HTTP_200_OK)

self.write(response)

exceptValueErrorase:

print("I'vefailedsettingthe{0}'sbrightness

level".format(led.description))

self.set_status(status.HTTP_400_BAD_REQUEST)

response={

'error':e.args[0]

}

self.write(response)

TheLedHandlerclassisasubclassoftornado.web.RequestHandler.TheclassoverridestheSUPPORTED_METHODSclassvariablewithatuplethatindicatestheclassjustsupportstheGETandPATCHmethods.Inaddition,theclassdeclaresthefollowingtwomethodsthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestonthisHTTPhandler:

get:ThismethodreceivestheidoftheLEDwhosestatushastoberetrievedintheidargument.Ifthereceivedidisn'toneofthekeysofthedrone.ledsdictionary,thecodecallstheself.set_statusmethodwithstatus.HTTP_404_NOT_FOUNDasanargumenttosetthestatuscodefortheresponsetoHTTP404NotFound.Otherwise,thecoderetrievesthevalueassociatedwiththekeywhosevaluematchestheidinthedrone.ledsdictionaryandsavestheretrievedLightEmittingDiodeinstanceintheledvariable.ThecodeprintsamessageindicatingthatitstartedretrievingtheLED'sbrightnesslevel,callstheled.get_brightness_levelmethodwithasynchronousexecution,andsavestheresultinthebrightness_levelvariable.Then,thecodewritesamessageindicatingthatitfinishedretrievingthebrightnesslevelandgeneratesaresponsedictionarywiththe'id','description',and'brightness_level'keysandtheirvalues.Finally,thecodecallstheself.set_statusmethodwithstatus.HTTP_200_OKasanargumenttosetthestatuscodefortheresponsetoHTTP200OKandcallstheself.writemethodwiththeresponsedictionaryasanargument.Sinceresponseisadictionary,TornadoautomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Typeheadertoapplication/json.patch:ThismethodreceivestheidoftheLEDthathastobeupdatedorpatchedintheidargument.Ashappenedinthepreviouslyexplainedgetmethod,thecodereturnsanHTTP404NotFoundincasethereceivediddoesn'tmatchtheanyofthekeysofthedrone.ledsdictionary.Otherwise,thecodecallsthetornado.escape.json_decodemethodwithself.request.bodyasanargumenttogeneratePythonobjectsfortheJSONstringoftherequestbodyandsavesthegenerateddictionaryintherequest_datavariable.Ifthedictionarydoesn'tincludeakeynamed'brightness_level',thecodereturnsanHTTP400BadRequeststatuscode.Incasethereisakey,thecodeprintsa

Page 360: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

messageindicatingthatitstartedsettingtheLED'sbrightnesslevel,includingthedescriptionfortheLED,callsthedrone.hexacopter.set_brightness_levelmethodwithasynchronousexecution.Ifthevaluespecifiedforthebrightness_levelisnotvalid,aValueErrorexceptionwillbecaughtandthecodewillreturnanHTTP400BadRequeststatuscodeandthevalidationerrormessagesastheresponsebody.Otherwise,thecodewritesamessageindicatingitfinishedsettingtheLED'sbrightnessvalueandgeneratesaresponsedictionarywiththe'id','description',and'brightness_level'keysandtheirvalues.Finally,thecodecallstheself.set_statusmethodwithstatus.HTTP_200_OKasanargumenttosetthestatuscodefortheresponsetoHTTP200OKandcallstheself.writemethodwiththeresponsedictionaryasanargument.Sinceresponseisadictionary,TornadoautomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Typeheadertoapplication/json.

Now,wewillcreateanAltimeterHandlerclassthatwewillusetorepresentthealtimeterresource.Openthepreviouslycreatedapi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

classAltimeterHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET")

ALTIMETER_ID=1

defget(self,id):

ifint(id)isnotself.__class__.ALTIMETER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

return

print("I'vestartedretrievingthealtitude")

altitude=drone.altimeter.get_altitude()

print("I'vefinishedretrievingthealtitude")

response={

'altitude':altitude

}

self.set_status(status.HTTP_200_OK)

self.write(response)

TheAltimeterHandlerclassisasubclassoftornado.web.RequestHandler.TheclassoverridestheSUPPORTED_METHODSclassvariablewithatuplethatindicatestheclassjustsupportstheGETmethod.Inaddition,theclassdeclaresthegetmethodthatwillbecalledwhentheHTTPmethodwiththesamenamearrivesasarequestonthisHTTPhandler.

Thegetmethodreceivestheidofthealtimeterwhosealtitudehastoberetrievedintheidargument.Ifthereceivediddoesn'tmatchthevalueoftheALTIMETER_IDclassattribute,thecodecallstheself.set_statusmethodwithstatus.HTTP_404_NOT_FOUNDasanargumenttosetthestatuscodefortheresponsetoHTTP404NotFound.Otherwise,thecodeprintsamessageindicatingthatitstartedretrievingthealtimeter'saltitude,callsthedrone.hexacopter.get_altitudemethodwithasynchronousexecution,andsavestheresultinthealtitudevariable.Then,thecodewritesamessageindicatingitfinishedretrievingthealtitudeandgeneratesaresponsedictionarywiththe'altitude'keyanditsvalue.Finally,thecodecallstheself.set_statusmethodwithstatus.HTTP_200_OKasanargumenttosetthe

Page 361: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

statuscodefortheresponsetoHTTP200OKandcallstheself.writemethodwiththeresponsedictionaryasanargument.Sinceresponseisadictionary,TornadoautomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Typeheadertoapplication/json.

ThefollowingtableshowsthemethodofourpreviouslycreatedHTTPhandlerclassesthatwewanttobeexecutedforeachcombinationofHTTPverbandscope:

HTTPverb Scope Classandmethod

GET Hexacopter HexacopterHandler.get

PATCH Hexacopter HexacopterHandler.patch

GET LED LedHandler.get

PATCH LED LedHandler.patch

GET Altimeter AltimeterHandler.get

IftherequestresultsintheinvocationofanHTTPhandlerclasswithanunsupportedHTTPmethod,TornadowillreturnaresponsewiththeHTTP405MethodNotAllowedstatuscode.

Page 362: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MappingURLpatternstorequesthandlersWemustmapURLpatternstoourpreviouslycodedsubclassesoftornado.web.RequestHandler.Thefollowinglinescreatethemainentrypointfortheapplication,initializeitwiththeURLpatternsfortheAPI,andstartslisteningforrequests.Openthepreviouslycreatedapi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_09_01folder:

application=web.Application([

(r"/hexacopters/([0-9]+)",HexacopterHandler),

(r"/leds/([0-9]+)",LedHandler),

(r"/altimeters/([0-9]+)",AltimeterHandler),

],debug=True)

if__name__=="__main__":

port=8888

print("Listeningatport{0}".format(port))

application.listen(port)

ioloop.IOLoop.instance().start()

Theprecedingcodecreatesaninstanceoftornado.web.ApplicationnamedapplicationwiththecollectionofrequesthandlersthatmakeuptheWebapplication.ThecodepassesalistoftuplestotheApplicationconstructor.Thelistiscomposedofaregularexpression(regexp)andatornado.web.RequestHandlersubclass(request_class).Inaddition,thecodesetsthedebugargumenttoTruetoenabledebugging.

Themainmethodcallstheapplication.listenmethodtobuildanHTTPserverfortheapplicationwiththedefinedrulesonthespecifiedport.Inthiscase,thecodespecifies8888astheport,savedintheportvariable,whichisthedefaultportforTornadoHTTPservers.Then,thecalltotornado.ioloop.IOLoop.instance().start()startstheservercreatedwiththepreviouscalltotheapplication.listenmethod.

Tip

AswithanyotherWebframework,youshouldneverenabledebugginginaproductionenvironment.

Page 363: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MakingHTTPrequeststotheTornadoAPINow,wecanruntheapi.pyscriptthatlaunchesTornados'sdevelopmentservertocomposeandsendHTTPrequeststoourunsecureandsimpleWebAPI.Executethefollowingcommand:

pythonapi.py

Thefollowinglinesshowtheoutputafterweexecutethepreviouscommand.TheTornadoHTTPdevelopmentserverislisteningatport8888:

Listeningatport8888

Withthepreviouscommand,wewillstarttheTornadoHTTPserveranditwilllistenoneveryinterfaceonport8888.Thus,ifwewanttomakeHTTPrequeststoourAPIfromothercomputersordevicesconnectedtoourLAN,wedon'tneedanyadditionalconfigurations.

Tip

IfyoudecidetocomposeandsendHTTPrequestsfromothercomputersordevicesconnectedtotheLAN,rememberthatyouhavetousethedevelopmentcomputer'sassignedIPaddressinsteadoflocalhost.Forexample,ifthecomputer'sassignedIPv4IPaddressis192.168.1.103,insteadoflocalhost:8888,youshoulduse192.168.1.103:8888.Ofcourse,youcanalsousethehostnameinsteadoftheIPaddress.ThepreviouslyexplainedconfigurationsareveryimportantbecausemobiledevicesmightbetheconsumersofourRESTfulAPIsandwewillalwayswanttotesttheappsthatmakeuseofourAPIsinourdevelopmentenvironments.

TheTornadoHTTPserverisrunningonlocalhost(127.0.0.1),listeningonport8888,andwaitingforourHTTPrequests.Now,wewillcomposeandsendHTTPrequestslocallyinourdevelopmentcomputerorfromothercomputerordevicesconnectedtoourLAN.

Page 364: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Workingwithcommand-linetools–curlandhttpieWewillstartcomposingandsendingHTTPrequestswiththecommand-linetoolswehaveintroducedinChapter1,DevelopingRESTfulAPIswithDjango,curlandHTTPie.Incaseyouhaven'tinstalledHTTPie,makesureyouactivatethevirtualenvironmentandthenrunthefollowingcommandintheterminalorCommandPrompttoinstalltheHTTPiepackage:

pipinstall--upgradehttpie

Tip

Incaseyoudon'trememberhowtoactivatethevirtualenvironmentthatwecreatedforthisexample,readthefollowingsectioninthischapter—€”SettingupthevirtualenvironmentwithDjangoRESTFramework.

OpenaCygwinterminalinWindowsoraTerminalinmacOSorLinuxandrunthefollowingcommand.WewillcomposeandsendanHTTPrequesttoturnonthehexacopterandsetitsmotorspeedto100RPMs:

httpPATCH:8888/hexacopters/1motor_speed=100

Thefollowingistheequivalentcurlcommand.Itisveryimportanttousethe-H"Content-Type:application/json"optiontoindicatecurltosendthedataspecifiedafterthe-doptionasapplication/jsoninsteadofthedefaultapplication/x-www-form-urlencoded:

curl-iXPATCH-H"Content-Type:application/json"-d'{"motor_speed":100}'

:8888/hexacopters/1

TheprecedingcommandswillcomposeandsendthefollowingHTTPrequest,PATCHhttp://localhost:8888/hexacopters/1,withthefollowingJSONkey-valuepair:

{

"motor_speed":100

}

Therequestspecifies/hexacopters/1,andtherefore,Tornadowilliterateoverthelistoftupleswithregularexpressionsandrequestclassesanditwillmatch'/hexacopters/([0-9]+)'.TornadowillcreateaninstanceoftheHexacopterHandlerclassandruntheHexacopterHandler.patchmethodwith1asthevaluefortheidargument.AstheHTTPverbfortherequestisPATCH,Tornadocallsthepatchmethod.Ifthehexacopter'sspeedissuccessfullyset,themethodreturnsanHTTP200OKstatuscodeandthekey-valuepairswiththespeedandstatusfortherecentlyupdatedhexacopterserializedtoJSONintheresponsebody.ThefollowinglinesshowanexampleresponsefortheHTTPrequest:

HTTP/1.1200OK

Content-Length:33

Content-Type:application/json;charset=UTF-8

Date:Thu,08Sep201602:02:27GMT

Server:TornadoServer/4.4.1

Page 365: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

{

"speed":100,

"turned_on":true

}

WewillcomposeandsendanHTTPrequesttoretrievethestatusandthemotorspeedforthehexacopter.GobacktotheCygwinterminalinWindowsortheTerminalinmacOSorLinux,andrunthefollowingcommand:

http:8888/hexacopters/1

Thefollowingistheequivalentcurlcommand:

curl-iXGET-H:8888/hexacopters/1

TheprecedingcommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8888/hexacopters/1.Therequestspecifies/hexacopters/1,andtherefore,itwillmatch'/hexacopters/([0-9]+)'andruntheHexacopterHandler.getmethodwith1asthevaluefortheidargument.AstheHTTPverbfortherequestisGET,Tornadocallsthegetmethod.Themethodretrievesthehexacopter'sstatusandgeneratesaJSONresponsewiththekey-valuepairs.

ThefollowinglinesshowanexampleresponsefortheHTTPrequest.ThefirstlinesshowtheHTTPresponseheaders,includingthestatus(200OK)andtheContent-typeas(application/json).AftertheHTTPresponseheaders,wecanseethedetailsofthehexacopter'sstatusintheJSONresponse:

HTTP/1.1200OK

Content-Length:33

Content-Type:application/json;charset=UTF-8

Date:Thu,08Sep201602:26:00GMT

Etag:"ff152383ca6ebe97e5a136166f433fbe7f9b4434"

Server:TornadoServer/4.4.1

{

"speed":100,

"turned_on":true

}

Afterwerunthethreerequests,wewillseethefollowinglinesinthewindowthatisrunningtheTornadoHTTPserver.Theoutputshowstheresultsofexecutingtheprintstatementsthatdescribewhenthecodestartedsettingorretrievinginformationandwhenitfinished:

I'vestartedsettingthehexacopter'smotorspeed

I'vefinishedsettingthehexacopter'smotorspeed

I'vestartedretrievinghexacopter'sstatus

I'vefinishedretrievinghexacopter'sstatus

Thedifferentmethodswecodedintherequesthandlerclassesendupcallingtime.sleeptosimulateittakessometimefortheoperationswiththehexacopter.Inthiscase,ourcodeisrunningwithasynchronousexecution,andtherefore,eachtimewecomposeandsenda

Page 366: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

request,theTornadoserverisblockeduntiltheoperationwiththehexacopterfinishesandthemethodsendstheresponse.WewillcreateanewversionofthisAPIthatwilluseasynchronousexecutionlaterandwewillunderstandtheadvantagesofTornado'snon-blockingfeatures.However,first,wewillunderstandhowthesynchronousversionoftheAPIworks.

ThefollowingimageshowstwoTerminalwindowsside-by-sideonmacOS.TheTerminalwindowontheleft-handsideisrunningtheTornadoHTTPserveranddisplaysthemessagesprintedinthemethodsthatprocesstheHTTPrequests.TheTerminalwindowontheright-handsideisrunninghttpcommandstogeneratetheHTTPrequests.ItisagoodideatouseasimilarconfigurationtochecktheoutputwhilewecomposeandsendtheHTTPrequests:

Now,wewillcomposeandsendanHTTPrequesttoretrieveahexacopterthatdoesn'texist.Rememberthatwejusthaveonehexacopterinourdrone.Runthefollowingcommandtotrytoretrievethestatusforanhexacopterwithaninvalidid.Wemustmakesurethattheutilitiesdisplaytheheadersaspartoftheresponsetoseethereturnedstatuscode:

http:8888/hexacopters/8

Thefollowingistheequivalentcurlcommand:

curl-iXGET:8888/hexacopters/8

ThepreviouscommandswillcomposeandsendthefollowingHTTPrequest:GEThttp://localhost:8888/hexacopters/8.Therequestisthesameasthepreviousonewehaveanalyzed,withadifferentnumberfortheidparameter.TheserverwillruntheHexacopterHandler.getmethodwith8asthevaluefortheidargument.Theidisnotequalto1,andtherefore,thecodewillreturnanHTTP404NotFoundstatuscode.Thefollowing

Page 367: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

linesshowanexampleheaderresponsefortheHTTPrequest:

HTTP/1.1404NotFound

Content-Length:0

Content-Type:text/html;charset=UTF-8

Date:Thu,08Sep201604:31:53GMT

Server:TornadoServer/4.4.1

Page 368: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WorkingwithGUItools-PostmanandothersSofar,wehavebeenworkingwithtwoTerminal-basedorcommand-linetoolstocomposeandsendHTTPrequeststoourTornadoHTTPserver-cURLandHTTPie.Now,wewillworkwithoneoftheGUItoolsweusedwhencomposingandsendingHTTPrequeststotheDjangodevelopmentserverandtheFlaskdevelopmentserver:Postman.

Now,wewillusetheBuildertabinPostmantoeasilycomposeandsendHTTPrequeststolocalhost:8888andtesttheRESTfulAPIwiththisGUItool.RememberthatPostmandoesn'tsupportcurl-likeshorthandsforlocalhost,andtherefore,wecannotusethesameshorthandswehavebeenusingwhencomposingrequestswithcurlandHTTPie.

SelectGET inthedrop-downmenuattheleft-handsideoftheEnterrequestURLtextboxandenterlocalhost:8888/leds/1inthistextboxattheright-handsideofthedropdown.Now,clickonSendandPostmanwilldisplaythestatus(200OK),thetimeittookfortherequesttobeprocessedandtheresponsebodywithallthegamesformattedasJSONwithsyntaxhighlighting(Prettyview).

ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPGETrequest:

ClickonHeadersontheright-handsideofBodyandCookiestoreadtheresponseheaders.ThefollowingscreenshotshowsthelayoutfortheresponseheadersthatPostmandisplaysforthepreviousresponse.NotethatPostmandisplaystheStatusattheright-handsideoftheresponseanddoesn'tincludeitasthefirstlineoftheHeaders,asithappenedwhenweworked

Page 369: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

withboththecURLandHTTPieutilities:

Now,wewillusetheBuildertabinPostmantocomposeandsendanHTTPrequesttocreateanewmessage,specifically,aPATCHrequest.Followthenextsteps:

1. SelectPATCHfromthedrop-downmenuontheleft-handsideoftheEnterrequestURLtextboxandenterlocalhost:8888/leds/1inthistextboxattheright-handsideofthedropdown.

2. ClickonBodyontheright-handsideofAuthorizationandHeaders,withinthepanelthatcomposestherequest.

3. ActivatetherawradiobuttonandselectJSON(application/json)inthedropdownontheright-handsideofthebinaryradiobutton.PostmanwillautomaticallyaddaContent-type=application/jsonheader,andtherefore,youwillnoticetheHeaderstabwillberenamedtoHeaders(1),indicatingusthatthereisonekey-valuepairspecifiedfortherequestheaders.

4. Enterthefollowinglinesinthetextboxbelowtheradiobuttons,withintheBodytab:

{

"brightness_level":128

}

ThefollowingscreenshotshowstherequestbodyinPostman:

Page 370: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WefollowedthenecessarystepstocreateanHTTPPATCHrequestwithaJSONbodythatspecifiesthenecessarykey-valuepairstocreateanewgame.ClickonSendandPostmanwilldisplaytheStatus(200OK),thetimeittookfortherequesttobeprocessed,andtheresponsebodywiththerecentlyaddedgameformattedasJSONwithsyntaxhighlighting(Prettyview).ThefollowingscreenshotshowstheJSONresponsebodyinPostmanfortheHTTPPOSTrequest.

TheTornadoHTTPserverislisteningoneveryinterfaceonport8888,andtherefore,wecanalsouseappsthatcancomposeandsendHTTPrequestsfrommobiledevicestoworkwiththeRESTfulAPI.Forexample,wecanworkwiththepreviouslyintroducediCurlHTTPapponiOSdevicessuchasiPadProandiPhone.InAndroiddevices,wecanworkwiththepreviouslyintroducedHTTPRequestApp.

ThefollowingscreenshotshowstheresultsofcomposingandsendingthefollowingHTTPrequestwiththeiCurlHTTPapp—GEThttp://192.168.2.3:8888/altimeters/1.RememberthatyouhavetoperformthepreviouslyexplainedconfigurationsinyourLANandrouterto

Page 371: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

beabletoaccesstheFlaskdevelopmentserverfromotherdevicesconnectedtoyourLAN.Inthiscase,theIPassignedtothecomputerrunningtheTornadoHTTPserveris192.168.2.3,andtherefore,youmustreplacethisIPwiththeIPassignedtoyourdevelopmentcomputer:

Page 372: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. ThemainbuildingblocksforaRESTfulAPIinTornadoaresubclassesofwhichthe

followingclasses:1. tornado.web.GenericHandler2. tornado.web.RequestHandler3. tornado.web.IncomingHTTPRequestHandler

2. IfwejustwanttosupporttheGETandPATCHmethods,wecanoverridetheSUPPORTED_METHODSclassvariablewithwhichofthefollowingvalues:1. ("GET","PATCH")2. {0:"GET",1:"PATCH"}3. {"GET":True,"PATCH":True,"POST":False,"PUT":False}

3. Thelistoftuplesforathetornado.Web.Applicationconstructoriscomposedof:1. Aregularexpression(regexp)andatornado.web.RequestHandlersubclass

(request_class).2. Aregularexpression(regexp)andatornado.web.GenericHandlersubclass

(request_class).3. Aregularexpression(regexp)andatornado.web.IncomingHTTPRequestHandler

subclass(request_class).

4. Whenwecalltheself.writemethodwithadictionaryasanargumentinarequesthandler,Tornado:1. AutomaticallywritesthechunkasJSONbutwehavetomanuallysetthevalueofthe

Content-Typeheadertoapplication/json.2. Requiresustousethejson.dumpsmethodandsetthevalueoftheContent-Type

headertoapplication/json.3. AutomaticallywritesthechunkasJSONandsetsthevalueoftheContent-Type

headertoapplication/json.

5. Acallstothetornado.escape.json_decodemethodwithself.request.bodyasanargumentinarequesthandler:1. GeneratesPythonobjectsfortheJSONstringoftherequestbodyandreturnsthe

generatedtuple.2. GeneratesPythonobjectsfortheJSONstringoftherequestbodyandreturnsthe

generateddictionary.3. GeneratesPythonobjectsfortheJSONstringoftherequestbodyandreturnsthe

generatedlist.

Page 373: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,wedesignedaRESTfulAPItointeractwithslowsensorsandactuators.WedefinedtherequirementsforourAPI,understoodthetasksperformedbyeachHTTPmethod,andsetupavirtualenvironmentwithTornado.

WecreatedtheclassesthatrepresentadroneandwrotecodetosimulateslowI/OoperationsthatarecalledforeachHTTPrequestmethod,wroteclassesthatrepresentrequesthandlersandprocessthedifferentHTTPrequests,andconfiguredtheURLpatternstorouteURLstorequesthandlersandtheirmethods.

Finally,westartedTornadodevelopmentserver,usedcommand-linetoolstocomposeandsendHTTPrequeststoourRESTfulAPI,andanalyzedhoweachHTTPrequestswasprocessedinourcode.WealsoworkedwithGUItoolstocomposeandsendHTTPrequests.

NowthatweunderstandthebasicsofTornadotocreateRESTfulAPIs,wewilltakeadvantageofthenon-blockingfeaturescombinedwithasynchronousoperationsinTornadoinanewversionfortheAPI,whichiswhatwearegoingtodiscussinthenextchapter.

Page 374: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter10.WorkingwithAsynchronousCode,Testing,andDeployinganAPIwithTornadoInthischapter,wewilltakeadvantageofthenon-blockingfeaturescombinedwithasynchronousoperationsinTornadoinanewversionfortheAPIwebuiltinthepreviouschapter.Wewillconfigure,write,andexecuteunittestsandlearnafewthingsrelatedtodeployment.Wewillcoverthefollowingtopics:

UnderstandingsynchronousandasynchronousexecutionWorkingwithasynchronouscodeRefactoringcodetotakeadvantageofasynchronousdecoratorsMappingURLpatternstoasynchronousandnon-blockingrequesthandlersMakingHTTPrequeststotheTornadonon-blockingAPISettingupunittestsWritingafirstroundofunittestsRunningunittestswithnose2andcheckingtestingcoverageImprovingtestingcoverage

Page 375: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

UnderstandingsynchronousandasynchronousexecutionInourcurrentversionoftheAPI,eachHTTPrequestisblocking,ashappenedwithDjangoandFlask.Thus,whenevertheTornadoHTTPserverreceivesanHTTPrequest,itdoesn'tstartworkingonanyotherHTTPrequestintheincomingqueueuntiltheserversendstheresponseforthefirstHTTPrequestitreceived.Themethodswecodedintherequesthandlersareworkingwithasynchronousexecutionandtheydon'ttakeadvantageofthenon-blockingfeaturesincludedinTornadowhencombinedwithasynchronousexecutions.

InordertosetthebrightnesslevelforboththeblueandwhiteLEDs,wehavetomaketwoHTTPPATCHrequests.WewillmakethemtounderstandhowourcurrentversionoftheAPIprocessestwoincomingrequests.

OpentwoCygwinterminalsinWindowsortwoTerminalsinmacOSorLinux,andwritethefollowingcommandinthefirstone.WewillcomposeandsendanHTTPrequesttosetthebrightnesslevelfortheblueLEDto255.Writethelineinthefirstwindow,butdon'tpressEnteryet,aswewilltrytolaunchtwocommandsatalmostthesametimeintwowindows:

httpPATCH:8888/leds/1brightness_level=255

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d

'{"brightness_level":255}':8888/leds/1

Now,gotothesecondwindowandwritethefollowingcommand.WewillcomposeandsendanHTTPrequesttosetthebrightnesslevelforthewhiteLEDto255.Writethelineinthesecondwindow,butdon'tpressEnteryet,aswewilltrytolaunchtwocommandsatalmostthesametimeintwowindows:

httpPATCH:8888/leds/2brightness_level=255

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d

'{"brightness_level":255}':8888/leds/2

Now,gotothefirstwindow,pressEnter.Then,gotothesecondwindowandquicklypressEnter.YouwillseethefollowinglineinthewindowthatisrunningtheTornadoHTTPserver:

I'vestartedsettingtheBlueLED'sbrightnesslevel

Then,youwillseethefollowinglinesthatshowtheresultsofexecutingtheprintstatementsthatdescribewhenthecodefinishedandthenstartedsettingthebrightnesslevelfortheLEDs:

I'vefinishedsettingtheBlueLED'sbrightnesslevel

Page 376: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

I'vestartedsettingtheWhiteLED'sbrightnesslevel

I'vefinishedsettingtheWhiteLED'sbrightnesslevel

ItwasnecessarytowaitfortherequestthatchangedthebrightnesslevelfortheblueLEDtofinishbeforetheservercouldprocesstheHTTPthatchangesthebrightnesslevelforthewhiteLED.ThefollowingscreenshotshowsthreewindowsonWindows.Thewindowontheleft-handsideisrunningtheTornadoHTTPserveranddisplaysthemessagesprintedinthemethodsthatprocesstheHTTPrequests.Thewindowattheupper-rightcornerisrunningthehttpcommandtogeneratetheHTTPrequestthatchangesthebrightnesslevelfortheblueLED.Thewindowatthelower-rightcornerisrunningthehttpcommandtogeneratetheHTTPrequestthatchangesthebrightnesslevelforthewhiteLED.ItisagoodideatouseasimilarconfigurationtochecktheoutputwhilewecomposeandsendtheHTTPrequestsandhowthesynchronousexecutionisworkingonthecurrentversionoftheAPI:

Tip

Rememberthatthedifferentmethodswecodedintherequesthandlerclassesendupcallingtime.sleeptosimulateittakessometimefortheoperationstocompletetheirexecution.

AseachoperationtakessometimeandblocksthepossibilitytoprocessotherincomingHTTPrequests,wewillcreateanewversionofthisAPIthatwilluseasynchronousexecution,andwewillunderstandtheadvantagesofTornado'snon-blockingfeatures.Thisway,itwillbepossibletochangethebrightnesslevelforthewhiteLEDwhiletheotherrequestisto

Page 377: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

changethebrightnesslevelfortheblueLED.TornadowillbeabletostartprocessingrequestswhiletheI/Ooperationswiththedronetakesometimetocomplete.

Page 378: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

RefactoringcodetotakeadvantageofasynchronousdecoratorsItisextremelydifficulttoreadandunderstandcodesplitintodifferentmethods,suchastheasynchronouscodethatrequiresworkingwithcallbacksthatareexecutedoncetheasynchronousexecutionfinishes.Luckily,Tornadoprovidesagenerator-basedinterfacethatenablesustowriteasynchronouscodeinrequesthandlersinasinglegenerator.Wecanavoidsplittingourmethodsintomultiplemethodswithcallbacksbyusingthetornado.gengenerator-basedinterfacethatTornadoprovidestomakeiteasiertoworkinanasynchronousenvironment.

TherecommendedwaytowriteasynchronouscodeinTornadoistousecoroutines.Thus,wewillrefactorourexistingcodetousethe@tornado.gen.coroutinedecoratorforasynchronousgeneratorsintherequiredmethodsthatprocessthedifferentHTTPrequestsinthesubclassesoftornado.web.RequestHandler.

Tip

Insteadofworkingwithachainofcallbacks,coroutinesusethePythonyieldkeywordtosuspendandresumeexecution.Byusingcoroutines,ourcodeisgoingtobeassimpletounderstandandmaintainasifwewerewritingsynchronouscode.

Wewilluseaninstanceoftheconcurrent.futures.ThreadPoolExecutorclassthatprovidesuswithahigh-levelinterfaceforasynchronouslyexecutingcallables.Theasynchronousexecutionwillbeperformedwiththreads.Wewillalsousethe@tornado.concurrent.run_on_executordecoratortorunasynchronousmethodasynchronouslyonanexecutor.Inthiscase,themethodsprovidedbythedifferentcomponentsofourdronetogetandsetdatahaveasynchronousexecution.Wewantthemtorunwithanasynchronousexecution.

Createanewasync_api.pyfile.Thefollowinglinesshowallthenecessaryimportsfortheclassesthatwewillcreateandthecodethatcreatesaninstanceoftheconcurrent.futures.ThreadPoolExecutorclassnamedthread_pool.Wewillusethisinstanceinthedifferentmethodsthatwewillrefactortomakeasynchronouscalls.Thecodefileforthesampleisincludedintherestful_python_chapter_10_01folder:

importstatus

fromdatetimeimportdate

fromtornadoimportweb,escape,ioloop,httpclient,gen

fromconcurrent.futuresimportThreadPoolExecutor

fromtornado.concurrentimportrun_on_executor

fromdroneimportAltimeter,Drone,Hexacopter,LightEmittingDiode

thread_pool=ThreadPoolExecutor()

drone=Drone()

Page 379: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Now,wewillcreateanAsyncHexacopterHandlerclassthatwewillusetohandlerequestsforthehexacopterresourcewithanasynchronousexecution.ThelinesthatareneworchangedcomparedwiththesynchronousversionofthishandlernamedHexacopterHandlerarehighlighted.Openthepreviouslycreatedasync_pi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_10_01folder:

classAsyncHexacopterHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET","PATCH")

HEXACOPTER_ID=1

_thread_pool=thread_pool

@gen.coroutine

defget(self,id):

ifint(id)isnotself.__class__.HEXACOPTER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

self.finish()

return

print("I'vestartedretrievinghexacopter'sstatus")

hexacopter_status=yieldself.retrieve_hexacopter_status()

print("I'vefinishedretrievinghexacopter'sstatus")

response={

'speed':hexacopter_status.motor_speed,

'turned_on':hexacopter_status.turned_on,

}

self.set_status(status.HTTP_200_OK)

self.write(response)

self.finish()

@run_on_executor(executor="_thread_pool")

defretrieve_hexacopter_status(self):

returndrone.hexacopter.get_hexacopter_status()

@gen.coroutine

defpatch(self,id):

ifint(id)isnotself.__class__.HEXACOPTER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

self.finish()

return

request_data=escape.json_decode(self.request.body)

if('motor_speed'notinrequest_data.keys())or\

(request_data['motor_speed']isNone):

self.set_status(status.HTTP_400_BAD_REQUEST)

self.finish()

return

try:

motor_speed=int(request_data['motor_speed'])

print("I'vestartedsettingthehexacopter'smotorspeed")

hexacopter_status=yield

self.set_hexacopter_motor_speed(motor_speed)

print("I'vefinishedsettingthehexacopter'smotorspeed")

response={

'speed':hexacopter_status.motor_speed,

'turned_on':hexacopter_status.turned_on,

}

self.set_status(status.HTTP_200_OK)

self.write(response)

self.finish()

exceptValueErrorase:

Page 380: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

print("I'vefailedsettingthehexacopter'smotorspeed")

self.set_status(status.HTTP_400_BAD_REQUEST)

response={

'error':e.args[0]

}

self.write(response)

self.finish()

@run_on_executor(executor="_thread_pool")

defset_hexacopter_motor_speed(self,motor_speed):

returndrone.hexacopter.set_motor_speed(motor_speed)

TheAsyncHexacopterHandlerclassdeclaresa_thread_poolclassattributethatsavesareferencetothepreviouslycreatedconcurrent.futures.ThreadPoolExecutorinstance.Theclassdeclarestwomethodswiththe@run_on_executor(executor="_thread_pool")decoratorthatmakesthesynchronousmethodrunasynchronouslywiththeconcurrent.futures.ThreadPoolExecutorinstancewhosereferenceissavedinthe_thread_poolclassattribute.Thefollowingarethetwomethods:

retrieve_hexacopter_status:Thismethodreturnstheresultsofcallingthedrone.hexacopter.get_hexacopter_statusmethod.set_hexacopter_motor_speed:Thismethodreceivesthemotor_speedargumentandreturnstheresultsofcallingthedrone.hexacopter.set_motor_speedmethodwiththereceivedmotor_speedasanargument.

Weaddedthe@gen.coroutinedecoratortoboththegetandpatchmethods.Weaddedacalltoself.finishwheneverwewantedtofinishtheHTTPrequest.ItisourresponsibilitytocallthismethodtofinishtheresponseandendtheHTTPrequestwhenweusethe@gen.coroutinedecorator.

Thegetmethodusesthefollowinglinetoretrievethehexacopterstatuswithanon-blockingandasynchronousexecution:

hexacopter_status=yieldself.retrieve_hexacopter_status()

ThecodeusestheyieldkeywordtoretrieveHexacopterStatusfromtheFuturereturnedbyself.retrieve_hexacopter_statusthatrunswithanasynchronousexecution.AFutureencapsulatestheasynchronousexecutionofacallable.Inthiscase,Futureencapsulatestheasynchronousexecutionoftheself.retrieve_hexacopter_statusmethod.Thenextlinesdidn'trequirechanges,andweonlyhadtoaddacalltoself.finishasthelastlineafterwewritetheresponse.

Thegetmethodusesthefollowinglinetoretrievethehexacopterstatuswithanon-blockingandasynchronousexecution:

hexacopter_status=yieldself.retrieve_hexacopter_status()

ThecodeusestheyieldkeywordtoretrievetheHexacopterStatusfromtheFuturereturnedbytheself.retrieve_hexacopter_statusthatrunswithanasynchronousexecution.

Page 381: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Thepatchmethodusesthefollowinglinetosetthehexacopter'smotorspeedwithanon-blockingandasynchronousexecution:

hexacopter_status=yieldself.set_hexacopter_motor_speed(motor_speed)

ThecodeusestheyieldkeywordtoretrievetheHexacopterStatusfromtheFuturereturnedbytheself.set_hexacopter_motor_speedthatrunswithanasynchronousexecution.Thenextlinesdidn'trequirechanges,andweonlyhadtoaddacalltoself.finishasthelastlineafterwewritetheresponse.

Now,wewillcreateanAsyncLedHandlerclassthatwewillusetorepresenttheLEDresourcesandprocessrequestswithanasynchronousexecution.ThelinesthatareneworchangedcomparedwiththesynchronousversionofthishandlernamedLedHandlerarehighlighted.Openthepreviouslycreatedasync_pi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_10_01folder:

classAsyncLedHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET","PATCH")

_thread_pool=thread_pool

@gen.coroutine

defget(self,id):

int_id=int(id)

ifint_idnotindrone.leds.keys():

self.set_status(status.HTTP_404_NOT_FOUND)

self.finish()

return

led=drone.leds[int_id]

print("I'vestartedretrieving{0}'sstatus".format(led.description))

brightness_level=yield

self.retrieve_led_brightness_level(led)

print("I'vefinishedretrieving{0}'sstatus".format(led.description))

response={

'id':led.identifier,

'description':led.description,

'brightness_level':brightness_level

}

self.set_status(status.HTTP_200_OK)

self.write(response)

self.finish()

@run_on_executor(executor="_thread_pool")

defretrieve_led_brightness_level(self,led):

returnled.get_brightness_level()

@gen.coroutine

defpatch(self,id):

int_id=int(id)

ifint_idnotindrone.leds.keys():

self.set_status(status.HTTP_404_NOT_FOUND)

self.finish()

return

led=drone.leds[int_id]

request_data=escape.json_decode(self.request.body)

if('brightness_level'notinrequest_data.keys())or\

Page 382: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

(request_data['brightness_level']isNone):

self.set_status(status.HTTP_400_BAD_REQUEST)

self.finish()

return

try:

brightness_level=int(request_data['brightness_level'])

print("I'vestartedsettingthe{0}'sbrightness

level".format(led.description))

yieldself.set_led_brightness_level(led,brightness_level)

print("I'vefinishedsettingthe{0}'sbrightness

level".format(led.description))

response={

'id':led.identifier,

'description':led.description,

'brightness_level':brightness_level

}

self.set_status(status.HTTP_200_OK)

self.write(response)

self.finish()

exceptValueErrorase:

print("I'vefailedsettingthe{0}'sbrightness

level".format(led.description))

self.set_status(status.HTTP_400_BAD_REQUEST)

response={

'error':e.args[0]

}

self.write(response)

self.finish()

@run_on_executor(executor="_thread_pool")

defset_led_brightness_level(self,led,brightness_level):

returnled.set_brightness_level(brightness_level)

TheAsyncLedHandlerclassdeclaresa_thread_poolclassattributethatsavesareferencetothepreviouslycreatedconcurrent.futures.ThreadPoolExecutorinstance.Theclassdeclarestwomethodswiththe@run_on_executor(executor="_thread_pool")decoratorthatmakesthesynchronousmethodrunasynchronouslywiththeconcurrent.futures.ThreadPoolExecutorinstancewhosereferenceissavedinthe_thread_poolclassattribute.Thefollowingarethetwomethods:

retrieve_led_brightness_level:ThismethodreceivesaLightEmittingDiodeinstanceintheledargumentandreturnstheresultsofcallingtheled.get_brightness_levelmethod.set_led_brightness_level:ThismethodreceivesaLightEmittingDiodeinstanceintheledargumentandthebrightness_levelargument.Thecodereturnstheresultsofcallingtheled.set_brightness_levelmethodwiththereceivedbrightness_levelasanargument.

Weaddedthe@gen.coroutinedecoratortoboththegetandpatchmethods.Inaddition,weaddedacalltoself.finishwheneverwewantedtofinishtheHTTPrequest.

ThegetmethodusesthefollowinglinetoretrievetheLED'sbrightnesslevelwithanon-blockingandasynchronousexecution:

Page 383: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

brightness_level=yieldself.retrieve_led_brightness_level(led)

ThecodeusestheyieldkeywordtoretrievetheintfromFuturereturnedbyself.retrieve_led_brightness_levelthatrunswithanasynchronousexecution.Thenextlinesdidn'trequirechanges,andweonlyhadtoaddacalltoself.finishasthelastlineafterwewritetheresponse.

Thepatchmethodusesthefollowinglinetoretrievethehexacopterstatuswithanon-blockingandasynchronousexecution:

hexacopter_status=yieldself.retrieve_hexacopter_status()

ThecodeusestheyieldkeywordtoretrieveHexacopterStatusfromFuturereturnedbyself.retrieve_hexacopter_statusthatrunswithanasynchronousexecution.

ThepatchmethodusesthefollowinglinetosettheLED'sbrightnesslevelwithanon-blockingandasynchronousexecution:

yieldself.set_led_brightness_level(led,brightness_level)

Thecodeusestheyieldkeywordtocallself.set_led_brightness_levelwithanasynchronousexecution.Thenextlinesdidn'trequirechanges,andweonlyhadtoaddacalltoself.finishasthelastlineafterwewritetheresponse.

Now,wewillcreateanAsyncAltimeterHandlerclassthatwewillusetorepresentthealtimeterresourceandprocessthegetrequestwithanasynchronousexecution.ThelinesthatareneworchangedcomparedwiththesynchronousversionofthishandlernamedAltimeterHandler,arehighlighted.Openthepreviouslycreatedasync_pi.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_10_01folder.

classAsyncAltimeterHandler(web.RequestHandler):

SUPPORTED_METHODS=("GET")

ALTIMETER_ID=1

_thread_pool=thread_pool

@gen.coroutine

defget(self,id):

ifint(id)isnotself.__class__.ALTIMETER_ID:

self.set_status(status.HTTP_404_NOT_FOUND)

self.finish()

return

print("I'vestartedretrievingthealtitude")

altitude=yieldself.retrieve_altitude()

print("I'vefinishedretrievingthealtitude")

response={

'altitude':altitude

}

self.set_status(status.HTTP_200_OK)

self.write(response)

self.finish()

Page 384: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

@run_on_executor(executor="_thread_pool")

defretrieve_altitude(self):

returndrone.altimeter.get_altitude()

TheAsyncAltimeterHandlerclassdeclaresa_thread_poolclassattributethatsavesareferencetothepreviouslycreatedconcurrent.futures.ThreadPoolExecutorinstance.Theclassdeclarestheretrieve_altitudemethodwiththe@run_on_executor(executor="_thread_pool")decoratorthatmakesthesynchronousmethodrunasynchronouslywiththeconcurrent.futures.ThreadPoolExecutorinstancewhosereferenceissavedinthe_thread_poolclassattribute.Theretrieve_altitudemethodreturnstheresultsofcallingthedrone.altimeter.get_altitudemethod.

[email protected],weaddedacalltoself.finishwheneverwewantedtofinishtheHTTPrequest.

Thegetmethodusesthefollowinglinetoretrievethealtimeter'saltitudevaluewithanon-blockingandasynchronousexecution:

altitude=yieldself.retrieve_altitude()

ThecodeusestheyieldkeywordtoretrievetheintfromFuturereturnedbyself.retrieve_altitudethatrunswithanasynchronousexecution.Thenextlinesdidn'trequirechanges,andweonlyhadtoaddacalltoself.finishasthelastlineafterwewritetheresponse.

Page 385: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MappingURLpatternstoasynchronousrequesthandlersWemustmapURLpatternstoourpreviouslycodedsubclassesoftornado.web.RequestHandlerthatprovideusasynchronousmethodsforourrequesthandlers.Thefollowinglinescreatethemainentrypointfortheapplication,initializeitwiththeURLpatternsfortheAPI,andstartlisteningforrequests.Openthepreviouslycreatedasync_api.pyfileandaddthefollowinglines.Thecodefileforthesampleisincludedintherestful_python_chapter_10_01folder:

application=web.Application([

(r"/hexacopters/([0-9]+)",AsyncHexacopterHandler),

(r"/leds/([0-9]+)",AsyncLedHandler),

(r"/altimeters/([0-9]+)",AsyncAltimeterHandler),

],debug=True)

if__name__=="__main__":

port=8888

print("Listeningatport{0}".format(port))

application.listen(port)

ioloop.IOLoop.instance().start()

Thecodecreatesaninstanceoftornado.web.ApplicationnamedapplicationwiththecollectionofrequesthandlersthatmakeuptheWebapplication.WejustchangedthenameofthehandlerswiththenewnamesthathavetheAsyncprefix.

Tip

AswithanyotherWebframework,youshouldneverenabledebugginginaproductionenvironment.

Page 386: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

MakingHTTPrequeststotheTornadonon-blockingAPINow,wecanruntheasync_api.pyscriptthatlaunchesTornados'sdevelopmentservertocomposeandsendHTTPrequeststoournewversionoftheWebAPIthatusesthenon-blockingfeaturesofTornadocombinedwithasynchronousexecution.Executethefollowingcommand:

pythonasync_api.py

Thefollowinglinesshowtheoutputafterweexecutethepreviouscommand.TheTornadoHTTPdevelopmentserverislisteningatport8888:

Listeningatport8888

Withthepreviouscommand,wewillstarttheTornadoHTTPserveranditwilllistenoneveryinterfaceonport8888.Thus,ifwewanttomakeHTTPrequeststoourAPIfromothercomputersordevicesconnectedtoourLAN,wedon'tneedanyadditionalconfigurations.

InournewversionoftheAPI,eachHTTPrequestisnon-blocking.Thus,whenevertheTornadoHTTPserverreceivesanHTTPrequestandmakesanasynchronouscall,itisabletostartworkingonanyotherHTTPrequestintheincomingqueuebeforetheserversendstheresponseforthefirstHTTPrequestitreceived.Themethodswecodedintherequesthandlersareworkingwithanasynchronousexecutionandtheytakeadvantageofthenon-blockingfeaturesincludedinTornado,combinedwithasynchronousexecutions.

InordertosetthebrightnesslevelforboththeblueandwhiteLEDs,wehavetomaketwoHTTPPATCHrequests.WewillmakethemtounderstandhowournewversionoftheAPIprocessestwoincomingrequests.

OpentwoCygwinterminalsinWindows,ortwoTerminalsinmacOSorLinux,andwritethefollowingcommandinthefirstone.WewillcomposeandsendanHTTPrequesttosetthebrightnesslevelfortheblueLEDto255.Writethelineinthefirstwindowbutdon'tpressEnteryet,aswewilltrytolaunchtwocommandsatalmostthesametimeintwowindows:

httpPATCH:8888/leds/1brightness_level=255

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d

'{"brightness_level":255}':8888/leds/1

Now,gotothesecondwindowandwritethefollowingcommand.WewillcomposeandsendanHTTPrequesttosetthebrightnesslevelforthewhiteLEDto255.Writethelineinthesecondwindowbutdon'tpressEnteryet,aswewilltrytolaunchtwocommandsatalmostthesametimeintwowindows:

Page 387: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

httpPATCH:8888/leds/2brightness_level=255

Thefollowingistheequivalentcurlcommand:

curl-iXPATCH-H"Content-Type:application/json"-d

'{"brightness_level":255}':8888/leds/2

Now,gotothefirstwindow,pressEnter.Then,gotothesecondwindowandquicklypressEnter.YouwillseethefollowinglinesinthewindowthatisrunningtheTornadoHTTPserver:

I'vestartedsettingtheBlueLED'sbrightnesslevel

I'vestartedsettingtheWhiteLED'sbrightnesslevel

Then,youwillseethefollowinglinesthatshowtheresultsofexecutingtheprintstatementsthatdescribewhenthecodefinishedsettingthebrightnesslevelfortheLEDs:

I'vefinishedsettingtheBlueLED'sbrightnesslevel

I'vefinishedsettingtheWhiteLED'sbrightnesslevel

TheservercouldstartprocessingtherequestthatchangesthebrightnesslevelforthewhiteLEDbeforetherequestthatchangesthebrightnessleveloftheblueLEDfinishesitsexecution.ThefollowingscreenshotshowsthreewindowsonWindows.Thewindowontheleft-handsideisrunningtheTornadoHTTPserveranddisplaysthemessagesprintedinthemethodsthatprocesstheHTTPrequests.Thewindowontheupper-rightcornerisrunningthehttpcommandtogeneratetheHTTPrequestthatchangesthebrightnesslevelfortheblueLED.Thewindowatthelower-rightcornerisrunningthehttpcommandtogeneratetheHTTPrequestthatchangesthebrightnesslevelforthewhiteLED.ItisagoodideatouseasimilarconfigurationtochecktheoutputwhilewecomposeandsendtheHTTPrequestsandcheckhowtheasynchronousexecutionisworkingonthenewversionoftheAPI:

Page 388: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Eachoperationtakessometimebutdoesn'tblockthepossibilitytoprocessotherincomingHTTPrequeststhankstothechangeswemadetotheAPItotakeadvantageoftheasynchronousexecution.Thisway,itispossibletochangethebrightnesslevelforthewhiteLEDwhiletheotherrequestistochangethebrightnesslevelfortheblueLED.TornadoisabletostartprocessingrequestswhiletheI/Ooperationswiththedronetakesometimetocomplete.

Page 389: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SettingupunittestsWewillusenose2tomakeiteasiertodiscoverandrununittests.Wewillmeasuretestcoverage,andtherefore,wewillinstallthenecessarypackagetoallowustoruncoveragewithnose2.First,wewillinstallthenose2andcov-corepackagesinourvirtualenvironment.Thecov-corepackagewillallowustomeasuretestcoveragewithnose2.

MakesureyouquittheTornado'sHTTPserver.RememberthatyoujustneedtopressCtrl+CintheTerminalorcommand-promptwindowinwhichitisrunning.Wejustneedtorunthefollowingcommandtoinstallthenose2packagethatwillalsoinstallthesixdependency:

pipinstallnose2

Thelastlinesfortheoutputwillindicatethatthenose2packagehasbeensuccessfullyinstalled:

Collectingnose2

Collectingsix>=1.1(fromnose2)

Downloadingsix-1.10.0-py2.py3-none-any.whl

Installingcollectedpackages:six,nose2

Successfullyinstallednose2-0.6.5six-1.10.0

Wejustneedtorunthefollowingcommandtoinstallthecov-corepackagethatwillalsoinstallthecoveragedependency:

pipinstallcov-core

Thelastlinesfortheoutputwillindicatethatthedjango-nosepackagehasbeensuccessfullyinstalled:

Collectingcov-core

Collectingcoverage>=3.6(fromcov-core)

Installingcollectedpackages:coverage,cov-core

Successfullyinstalledcov-core-1.15.0coverage-4.2

Openthepreviouslycreatedasync_api.pyfileandremovethelinesthatcreatetheweb.Applicationinstancenamedapplicationandthe__main__method.Afteryouremovetheselines,addthenextlines.Thecodefileforthesampleisincludedintherestful_python_chapter_10_02folder:

classApplication(web.Application):

def__init__(self,**kwargs):

handlers=[

(r"/hexacopters/([0-9]+)",AsyncHexacopterHandler),

(r"/leds/([0-9]+)",AsyncLedHandler),

(r"/altimeters/([0-9]+)",AsyncAltimeterHandler),

]

super(Application,self).__init__(handlers,**kwargs)

Page 390: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

if__name__=="__main__":

application=Application()

application.listen(8888)

tornado_ioloop=ioloop.IOLoop.instance()

ioloop.PeriodicCallback(lambda:None,500,tornado_ioloop).start()

tornado_ioloop.start()

ThecodedeclaresanApplicationclass,specifically,asubclassoftornado.web.Applicationthatoverridestheinheritedconstructor,thatis,the__init__method.TheconstructordeclaresthehandlerslistthatmapsURLpatternstoasynchronousrequesthandlersandthencallstheinheritedconstructorwiththelistasoneofitsarguments.Wecreatetheclasstomakeitpossiblefortheteststousethisclass.

Then,themainmethodcreatesaninstanceoftheApplicationclass,registersaperiodiccallbackthatwillbeexecutedevery500millisecondsbytheIOLooptomakeitpossibletouseCtrl+CtostoptheHTTPserver,andfinallycallsthestartmethod.Theasync_api.pyscriptisgoingtocontinueworkinginthesameway.ThemaindifferenceisthatwecanreusetheApplicationclassinourtests.

Finally,createanewtextfilenamed.coveragercwithinthevirtualenvironment'srootfolderwiththefollowingcontent.Thecodefileforthesampleisincludedintherestful_python_chapter_10_02folder:

[run]

include=async_api.py,drone.py

Thisway,thecoverageutilitywillonlyconsiderthecodeintheasync_api.pyanddrone.pyfileswhenprovidinguswiththetestcoveragereport.Wewillhaveamoreaccuratetestcoveragereportwiththissettingsfile.

Tip

Inthiscase,wewon'tbeusingconfigurationfilesforeachenvironment.However,inmorecomplexapplications,youwilldefinitelywanttouseconfigurationfiles.

Page 391: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

WritingafirstroundofunittestsNow,wewillwriteafirstroundofunittests.Specifically,wewillwriteunittestsrelatedtotheLEDresources.Createanewtestssubfolderwithinthevirtualenvironment'srootfolder.Then,createanewtest_hexacopter.pyfilewithinthenewtestssubfolder.AddthefollowinglinesthatdeclaremanyimportstatementsandtheTextHexacopterclass.Thecodefileforthesampleisincludedintherestful_python_chapter_10_02folder:

importunittest

importstatus

importjson

fromtornadoimportioloop,escape

fromtornado.testingimportAsyncHTTPTestCase,gen_test,gen

fromasync_apiimportApplication

classTestHexacopter(AsyncHTTPTestCase):

defget_app(self):

self.app=Application(debug=False)

returnself.app

deftest_set_and_get_led_brightness_level(self):

"""

EnsurewecansetandgetthebrightnesslevelsforbothLEDs

"""

patch_args_led_1={'brightness_level':128}

patch_args_led_2={'brightness_level':250}

patch_response_led_1=self.fetch(

'/leds/1',

method='PATCH',

body=json.dumps(patch_args_led_1))

patch_response_led_2=self.fetch(

'/leds/2',

method='PATCH',

body=json.dumps(patch_args_led_2))

self.assertEqual(patch_response_led_1.code,status.HTTP_200_OK)

self.assertEqual(patch_response_led_2.code,status.HTTP_200_OK)

get_response_led_1=self.fetch(

'/leds/1',

method='GET')

get_response_led_2=self.fetch(

'/leds/2',

method='GET')

self.assertEqual(get_response_led_1.code,status.HTTP_200_OK)

self.assertEqual(get_response_led_2.code,status.HTTP_200_OK)

get_response_led_1_data=escape.json_decode(get_response_led_1.body)

get_response_led_2_data=escape.json_decode(get_response_led_2.body)

self.assertTrue('brightness_level'inget_response_led_1_data.keys())

self.assertTrue('brightness_level'inget_response_led_2_data.keys())

self.assertEqual(get_response_led_1_data['brightness_level'],

patch_args_led_1['brightness_level'])

self.assertEqual(get_response_led_2_data['brightness_level'],

patch_args_led_2['brightness_level'])

Page 392: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

TheTestHexacopterclassisasubclassoftornado.testing.AsyncHTTPTestCase,thatis,atestcasethatstartsupaTornadoHTTPServer.Theclassoverridestheget_appmethodthatreturnsthetornado.web.Applicationinstancethatwewanttotest.Inthiscase,wereturnaninstanceoftheApplicationclassdeclaredintheasync_apimodule,withthedebugargumentsettoFalse.

Thetest_set_and_get_led_brightness_levelmethodtestswhetherwecansetandgetthebrightnesslevelsforboththewhiteandblueLED.ThecodecomposesandsendstwoHTTPPATCHmethodstosetnewbrightnesslevelvaluesfortheLEDswhoseIDsareequalto1and2.ThecodesetsadifferentbrightnesslevelforeachLED.

Thecodecallstheself.fetchmethodtocomposeandsendtheHTTPPATCHrequestandcallsjson.dumpswiththedictionarytobesenttothebodyasanargument.Then,thecodeusesself.fetchagaintocomposeandsendtwoHTTPGETmethodstoretrievethebrightnesslevelvaluesfortheLEDswhosebrightnessvalueshavebeenmodified.Thecodeusestornado.escape.json_decodetoconvertthebytesintheresponsebodytoaPythondictionary.ThemethodusesassertEqualandassertTruetocheckforthefollowingexpectedresults:

Thestatus_codeforthetwoHTTPPATCHresponsesisHTTP200OK(status.HTTP_200_OK)Thestatus_codeforthetwoHTTPGETresponsesisHTTP200OK(status.HTTP_200_OK)TheresponsebodyforthetwoHTTPGETresponsesincludeakeynamedbrigthness_level

Thevalueforthebrightness_levelkeyintheHTTPGETresponsesareequaltothebrightnesslevelsettoeachLED

Page 393: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Runningunittestswithnose2andcheckingtestingcoverageNow,runthefollowingcommandtocreateallthenecessarytablesinourtestdatabaseandusethenose2testrunningtoexecuteallthetestswecreated.ThetestrunnerwillexecuteallthemethodsforourTestHexacopterclassthatstartwiththetest_prefixandwilldisplaytheresults.Inthiscase,wejusthaveonemethodthatmatchesthecriteria,butwewilladdmorelater.

Runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing.Wewillusethe-voptiontoinstructnose2toprinttestcasenamesandstatuses.The--with-coverageoptionturnsontestcoveragereportinggeneration:

nose2-v--with-coverage

Thefollowinglinesshowthesampleoutput.Noticethatthenumbersshowninthereportmighthavesmalldifferencesifourcodeincludesadditionallinesorcomments:

test_set_and_get_led_brightness_level(test_hexacopter.TestHexacopter)...

I'vestartedsettingtheBlueLED'sbrightnesslevel

I'vefinishedsettingtheBlueLED'sbrightnesslevel

I'vestartedsettingtheWhiteLED'sbrightnesslevel

I'vefinishedsettingtheWhiteLED'sbrightnesslevel

I'vestartedretrievingBlueLED'sstatus

I'vefinishedretrievingBlueLED'sstatus

I'vestartedretrievingWhiteLED'sstatus

I'vefinishedretrievingWhiteLED'sstatus

ok

----------------------------------------------------------------

Ran1testin1.311s

OK

-----------coverage:platformwin32,python3.5.2-final-0-----

NameStmtsMissCover

----------------------------------

async_api.py1296947%

drone.py571868%

----------------------------------

TOTAL1868753%

Bydefault,nose2looksformoduleswhosenamesstartwiththetestprefix.Inthiscase,theonlymodulethatmatchesthecriteriaisthetest_hexacoptermodule.Inthemodulesthatmatchthecriteria,nose2loadstestsfromallthesubclassesofunittest.TestCaseandthefunctionswhosenamesstartwiththetestprefix.Thetornado.testing.AsyncHTTPTestCaseincludesunittest.TestCaseasoneofitssuperclassesintheclasshierarchy.

Theoutputprovideddetailsindicatingthatthetestrunnerdiscoveredandexecutedonetestanditpassed.TheoutputdisplaysthemethodnameandtheclassnameforeachmethodintheTestHexacopterclassthatstartedwiththetest_prefixandrepresentedatesttobeexecuted.

Page 394: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Wedefinitelyhaveaverylowcoverageforasync_api.pyanddrone.pybasedonthemeasurementsshowninthereport.Infact,wejustwroteonetestrelatedtoLEDs,andtherefore,itmakessensethatthecoveragehastobeimproved.Wedidn'tcreatetestsrelatedtootherhexacopterresources.

Wecanrunthecoveragecommandwiththe-mcommand-lineoptiontodisplaythelinenumbersofthemissingstatementsinanewMissingcolumn:

coveragereport-m

Thecommandwillusetheinformationfromthelastexecutionandwilldisplaythemissingstatements.Thenextlinesshowasampleoutputthatcorrespondstothepreviousexecutionoftheunittests.Noticethatthenumbersshowninthereportmighthavesmalldifferencesifourcodeincludesadditionallinesorcomments:

NameStmtsMissCoverMissing

--------------------------------------------

async_api.py1296947%137-150,154,158-187,191,202-204,

226-228,233-235,249-256,270-282,286,311-315

drone.py571868%11-12,24,27-34,37,40-41,59,61,68-

69

--------------------------------------------

TOTAL1868753%

Now,runthefollowingcommandtogetannotatedHTMLlistingsdetailingmissedlines:

coveragehtml

Opentheindex.htmlHTMLfilegeneratedinthehtmlcovfolderwithyourWebbrowser.ThefollowingscreenshotshowsanexamplereportthatcoveragegeneratedinHTMLformat:

Page 395: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Clickortapondrony.pyandtheWebbrowserwillrenderaWebpagethatdisplaysthestatementsthatwererun,themissingones,andtheexcludedones,withdifferentcolors.Wecanclickortapontherun,missing,andexcludedbuttonstoshoworhidethebackgroundcolorthatrepresentsthestatusforeachlineofcode.Bydefault,themissinglinesofcodewillbedisplayedwithapinkbackground.Thus,wemustwriteunitteststhattargettheselinesofcodetoimproveourtestcoverage.

Page 396: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

ImprovingtestingcoverageNow,wewillwriteadditionalunitteststoimprovethetestingcoverage.Specifically,wewillwriteunittestsrelatedtothehexacoptermotorandthealtimeter.Opentheexistingtest_hexacopter.pyfileandinsertthefollowinglinesafterthelastline.Thecodefileforthesampleisincludedintherestful_python_chapter_10_03folder:

deftest_set_and_get_hexacopter_motor_speed(self):

"""

Ensurewecansetandgetthehexacopter'smotorspeed

"""

patch_args={'motor_speed':700}

patch_response=self.fetch(

'/hexacopters/1',

method='PATCH',

body=json.dumps(patch_args))

self.assertEqual(patch_response.code,status.HTTP_200_OK)

get_response=self.fetch(

'/hexacopters/1',

method='GET')

self.assertEqual(get_response.code,status.HTTP_200_OK)

get_response_data=escape.json_decode(get_response.body)

self.assertTrue('speed'inget_response_data.keys())

self.assertTrue('turned_on'inget_response_data.keys())

self.assertEqual(get_response_data['speed'],

patch_args['motor_speed'])

self.assertEqual(get_response_data['turned_on'],

True)

deftest_get_altimeter_altitude(self):

"""

Ensurewecangetthealtimeter'saltitude

"""

get_response=self.fetch(

'/altimeters/1',

method='GET')

self.assertEqual(get_response.code,status.HTTP_200_OK)

get_response_data=escape.json_decode(get_response.body)

self.assertTrue('altitude'inget_response_data.keys())

self.assertGreaterEqual(get_response_data['altitude'],

0)

self.assertLessEqual(get_response_data['altitude'],

3000)

ThepreviouscodeaddedthefollowingtwomethodstotheTestHexacopterclasswhosenamesstartwiththetest_prefix:

test_set_and_get_hexacopter_motor_speed:Thistestswhetherwecansetandgetthehexacopter'smotorspeed.test_get_altimeter_altitude:Thistestswhetherwecanretrievethealtitudevaluefromthealtimeter.

Page 397: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Wejustcodedafewtestsrelatedtothehexacopterandthealtimeterinordertoimprovetestcoverageandnoticetheimpactonthetestcoveragereport.

Now,runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing:

nose2-v--with-coverage

Thefollowinglinesshowthesampleoutput.Noticethatthenumbersshowninthereportmighthavesmalldifferencesifourcodeincludesadditionallinesorcomments:

test_get_altimeter_altitude(test_hexacopter.TestHexacopter)...

I'vestartedretrievingthealtitude

I'vefinishedretrievingthealtitude

ok

test_set_and_get_hexacopter_motor_speed(test_hexacopter.TestHexacopter)...

I'vestartedsettingthehexacopter'smotorspeed

I'vefinishedsettingthehexacopter'smotorspeed

I'vestartedretrievinghexacopter'sstatus

I'vefinishedretrievinghexacopter'sstatus

ok

test_set_and_get_led_brightness_level(test_hexacopter.TestHexacopter)...

I'vestartedsettingtheBlueLED'sbrightnesslevel

I'vefinishedsettingtheBlueLED'sbrightnesslevel

I'vestartedsettingtheWhiteLED'sbrightnesslevel

I'vefinishedsettingtheWhiteLED'sbrightnesslevel

I'vestartedretrievingBlueLED'sstatus

I'vefinishedretrievingBlueLED'sstatus

I'vestartedretrievingWhiteLED'sstatus

I'vefinishedretrievingWhiteLED'sstatus

ok

--------------------------------------------------------------

Ran3testsin2.282s

OK

-----------coverage:platformwin32,python3.5.2-final-0---

NameStmtsMissCover

----------------------------------

async_api.py1293871%

drone.py57493%

----------------------------------

TOTAL1864277%

Theoutputprovideddetailsindicatingthatthetestrunnerexecuted3testsandallofthempassed.ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageincreasedtheCoverpercentageoftheasync_api.pymodulefrom47%inthepreviousrunto71%.Inaddition,thepercentageofthedrone.pymoduleincreasedfrom68%to93%becausewewroteteststhatworkedwithallthecomponentsforthedrone.Thenewadditionaltestswewroteexecutedadditionalcodeinthetwomodules,andtherefore,thereisanimpactinthecoveragereport.

Ifwetakealookatthemissingstatements,wewillnoticethatwearen'ttestingscenarioswherevalidationsfail.Now,wewillwriteadditionalunitteststoimprovethetestingcoveragefurther.Specifically,wewillwriteunitteststomakesurethatwecannotsetinvalidbrightness

Page 398: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

levelsfortheLEDs,wecannotsetinvalidmotorspeedsforthehexacopter,andwereceiveanHTTP404NotFoundstatuscodewhenwetrytoaccessaresourcethatdoesn'texist.Opentheexistingtest_hexacopter.pyfileandinsertthefollowinglinesafterthelastline.Thecodefileforthesampleisincludedintherestful_python_chapter_10_04folder:

deftest_set_invalid_brightness_level(self):

"""

EnsurewecannotsetaninvalidbrightnesslevelforaLED

"""

patch_args_led_1={'brightness_level':256}

patch_response_led_1=self.fetch(

'/leds/1',

method='PATCH',

body=json.dumps(patch_args_led_1))

self.assertEqual(patch_response_led_1.code,status.HTTP_400_BAD_REQUEST)

patch_args_led_2={'brightness_level':-256}

patch_response_led_2=self.fetch(

'/leds/2',

method='PATCH',

body=json.dumps(patch_args_led_2))

self.assertEqual(patch_response_led_2.code,status.HTTP_400_BAD_REQUEST)

patch_response_led_3=self.fetch(

'/leds/2',

method='PATCH',

body=json.dumps({}))

self.assertEqual(patch_response_led_3.code,status.HTTP_400_BAD_REQUEST)

deftest_set_brightness_level_invalid_led_id(self):

"""

EnsurewecannotsetthebrightnesslevelforaninvalidLEDid

"""

patch_args_led_1={'brightness_level':128}

patch_response_led_1=self.fetch(

'/leds/100',

method='PATCH',

body=json.dumps(patch_args_led_1))

self.assertEqual(patch_response_led_1.code,status.HTTP_404_NOT_FOUND)

deftest_get_brightness_level_invalid_led_id(self):

"""

EnsurewecannotgetthebrightnesslevelforaninvalidLEDid

"""

patch_response_led_1=self.fetch(

'/leds/100',

method='GET')

self.assertEqual(patch_response_led_1.code,status.HTTP_404_NOT_FOUND)

deftest_set_invalid_motor_speed(self):

"""

Ensurewecannotsetaninvalidmotorspeedforthehexacopter

"""

patch_args_hexacopter_1={'motor_speed':89000}

patch_response_hexacopter_1=self.fetch(

'/hexacopters/1',

method='PATCH',

Page 399: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

body=json.dumps(patch_args_hexacopter_1))

self.assertEqual(patch_response_hexacopter_1.code,

status.HTTP_400_BAD_REQUEST)

patch_args_hexacopter_2={'motor_speed':-78600}

patch_response_hexacopter_2=self.fetch(

'/hexacopters/1',

method='PATCH',

body=json.dumps(patch_args_hexacopter_2))

self.assertEqual(patch_response_hexacopter_2.code,

status.HTTP_400_BAD_REQUEST)

patch_response_hexacopter_3=self.fetch(

'/hexacopters/1',

method='PATCH',

body=json.dumps({}))

self.assertEqual(patch_response_hexacopter_3.code,

status.HTTP_400_BAD_REQUEST)

deftest_set_motor_speed_invalid_hexacopter_id(self):

"""

Ensurewecannotsetthemotorspeedforaninvalidhexacopterid

"""

patch_args_hexacopter_1={'motor_speed':128}

patch_response_hexacopter_1=self.fetch(

'/hexacopters/100',

method='PATCH',

body=json.dumps(patch_args_hexacopter_1))

self.assertEqual(patch_response_hexacopter_1.code,

status.HTTP_404_NOT_FOUND)

deftest_get_motor_speed_invalid_hexacopter_id(self):

"""

Ensurewecannotgetthemotorspeedforaninvalidhexacopterid

"""

patch_response_hexacopter_1=self.fetch(

'/hexacopters/5',

method='GET')

self.assertEqual(patch_response_hexacopter_1.code,

status.HTTP_404_NOT_FOUND)

deftest_get_altimeter_altitude_invalid_altimeter_id(self):

"""

Ensurewecannotgetthealtimeter'saltitudeforaninvalidaltimeter

id

"""

get_response=self.fetch(

'/altimeters/5',

method='GET')

self.assertEqual(get_response.code,status.HTTP_404_NOT_FOUND)

ThepreviouscodeaddedthefollowingsevenmethodstotheTestHexacopterclasswhosenamesstartwiththetest_prefix:

test_set_invalid_brightness_level:ThismakessurethatwecannotsetaninvalidbrightnesslevelforanLEDthroughanHTTPPATCHrequest.test_set_brightness_level_invalid_led_id:Thismakessurethatwecannotsetthe

Page 400: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

brightnesslevelforaninvalidLEDIDthroughanHTTPPATCHrequest.test_get_brightness_level_invalid_led_id:ThismakessurethatwecannotgetthebrightnesslevelforaninvalidLEDID.test_set_invalid_motor_speed:ThismakessurethatwecannotsetaninvalidmotorseedforthehexacopterthroughanHTTPPATCHrequest.test_set_motor_speed_invalid_hexacopter_id:ThismakessurethatwecannotsetthemotorspeedforaninvalidhexacopterIDthroughanHTTPPATCHrequest.test_get_motor_speed_invalid_hexacopter_id:ThismakessurethatwecannotgetthemotorspeedforaninvalidhexacopterID.test_get_altimeter_altitude_invalid_altimeter_id:ThismakessurethatwecannotgetthealtitudevalueforaninvalidaltimeterID.

Wecodedmanyteststhatwillmakesurethatallthevalidationsworkasexpected.Now,runthefollowingcommandwithinthesamevirtualenvironmentwehavebeenusing:

nose2-v--with-coverage

Thefollowinglinesshowthesampleoutput.Noticethatthenumbersshowninthereportmighthavesmalldifferencesifourcodeincludesadditionallinesorcomments:

I'vefinishedretrievingthealtitude

ok

test_get_altimeter_altitude_invalid_altimeter_id

(test_hexacopter.TestHexacopter)...WARNING:tornado.access:404GET

/altimeters/5(127.0.0.1)1.00ms

ok

test_get_brightness_level_invalid_led_id(test_hexacopter.TestHexacopter)...

WARNING:tornado.access:404GET/leds/100(127.0.0.1)2.01ms

ok

test_get_motor_speed_invalid_hexacopter_id(test_hexacopter.TestHexacopter)

...WARNING:tornado.access:404GET/hexacopters/5(127.0.0.1)2.01ms

ok

test_set_and_get_hexacopter_motor_speed(test_hexacopter.TestHexacopter)...

I'vestartedsettingthehexacopter'smotorspeed

I'vefinishedsettingthehexacopter'smotorspeed

I'vestartedretrievinghexacopter'sstatus

I'vefinishedretrievinghexacopter'sstatus

ok

test_set_and_get_led_brightness_level(test_hexacopter.TestHexacopter)...

I'vestartedsettingtheBlueLED'sbrightnesslevel

I'vefinishedsettingtheBlueLED'sbrightnesslevel

I'vestartedsettingtheWhiteLED'sbrightnesslevel

I'vefinishedsettingtheWhiteLED'sbrightnesslevel

I'vestartedretrievingBlueLED'sstatus

I'vefinishedretrievingBlueLED'sstatus

I'vestartedretrievingWhiteLED'sstatus

I'vefinishedretrievingWhiteLED'sstatus

ok

test_set_brightness_level_invalid_led_id(test_hexacopter.TestHexacopter)...

WARNING:tornado.access:404PATCH/leds/100(127.0.0.1)1.01ms

ok

test_set_invalid_brightness_level(test_hexacopter.TestHexacopter)...I've

Page 401: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

startedsettingtheBlueLED'sbrightnesslevel

I'vefailedsettingtheBlueLED'sbrightnesslevel

WARNING:tornado.access:400PATCH/leds/1(127.0.0.1)13.51ms

I'vestartedsettingtheWhiteLED'sbrightnesslevel

I'vefailedsettingtheWhiteLED'sbrightnesslevel

WARNING:tornado.access:400PATCH/leds/2(127.0.0.1)10.03ms

WARNING:tornado.access:400PATCH/leds/2(127.0.0.1)2.01ms

ok

test_set_invalid_motor_speed(test_hexacopter.TestHexacopter)...I'vestarted

settingthehexacopter'smotorspeed

I'vefailedsettingthehexacopter'smotorspeed

WARNING:tornado.access:400PATCH/hexacopters/1(127.0.0.1)19.27ms

I'vestartedsettingthehexacopter'smotorspeed

I'vefailedsettingthehexacopter'smotorspeed

WARNING:tornado.access:400PATCH/hexacopters/1(127.0.0.1)9.04ms

WARNING:tornado.access:400PATCH/hexacopters/1(127.0.0.1)1.00ms

ok

test_set_motor_speed_invalid_hexacopter_id(test_hexacopter.TestHexacopter)

...WARNING:tornado.access:404PATCH/hexacopters/100(127.0.0.1)1.00ms

ok

----------------------------------------------------------------------

Ran10testsin5.905s

OK

-----------coverage:platformwin32,python3.5.2-final-0-----------

NameStmtsMissCover

----------------------------------

async_api.py129596%

drone.py570100%

----------------------------------

TOTAL186597%

Theoutputprovideddetailsindicatingthatthetestrunnerexecuted10testsandallofthempassed.ThetestcodecoveragemeasurementreportprovidedbythecoveragepackageincreasedtheCoverpercentageoftheasync_api.pymodulefrom71%inthepreviousrunto97%.Inaddition,thepercentageofthedrone.pymoduleincreasedfrom93%to100%.Ifwecheckthecoveragereport,wewillnoticethattheonlystatementsthataren'texecutedarethestatementsincludedinthemainmethodfortheasync_api.pymodulebecausetheyaren'tpartofthetests.Thus,wecansaythatwehave100%coverage.

Nowthatwehaveagreattestcoverage,wecangeneratetherequirements.txtfilethatliststheapplicationdependenciestogetherwiththeirversions.Thisway,anyplatforminwhichwedecidetodeploytheRESTfulAPIwillbeabletoeasilyinstallallthenecessarydependencieslistedinthefile.

Runthefollowingpipfreezetogeneratetherequirements.txtfile:

pipfreeze>requirements.txt

Thefollowinglinesshowthecontentofasamplegeneratedrequirements.txtfile.However,bearinmindthatmanypackagesincreasetheirversionnumberquicklyandyoumightseedifferentversionsinyourconfiguration:

Page 402: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

cov-core==1.15.0

coverage==4.2

nose2==0.6.5

six==1.10.0

tornado==4.4.1

Page 403: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

OtherPythonWebframeworksforbuildingRESTfulAPIsWebuiltRESTfulWebServiceswithDjango,Flask,andTornado.However,PythonhasmanyotherWebframeworksthatarealsosuitableforbuildingRESTfulAPIs.Everythingwelearnedthroughoutthebookaboutdesigning,building,testing,anddeployingaRESTfulAPIisalsoapplicabletoanyotherPythonWebframeworkwedecidetouse.ThefollowinglistenumeratesadditionalframeworksandtheirmainWebpage:

Pyramid:http://www.pylonsproject.org/Bottle:http://bottlepy.org/Falcon:https://falconframework.org/

AsalwayshappenswithanyPythonWebframework,thereareadditionalpackagesthatmightsimplifyourmostcommontasks.Forexample,itispossibletouseRamsesincombinationwithPyramidtocreateRESTfulAPIsbyworkingwithRAML(RESTfulAPIModelingLanguage),whosespecificationisavailableathttp://github.com/raml-org/raml-spec.YoucanreadmoredetailsaboutRamsesathttp://ramses.readthedocs.io/en/stable/getting_started.html.

Page 404: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Testyourknowledge1. Theconcurrent.futures.ThreadPoolExecutorclassprovidesus:

1. Ahigh-levelinterfaceforsynchronouslyexecutingcallables.2. Ahigh-levelinterfaceforasynchronouslyexecutingcallables.3. Ahigh-levelinterfaceforcomposingHTTPrequests.

2. [email protected]_on_executordecoratorallowsusto:1. Runanasynchronousmethodsynchronouslyonanexecutor.2. RunanasynchronousmethodonanexecutorwithoutgeneratingaFuture.3. Runasynchronousmethodasynchronouslyonanexecutor.

3. TherecommendedwaytowriteasynchronouscodeinTornadoistouse:1. Coroutines.2. Chainedcallbacks.3. Subroutines.

4. Thetornado.Testing.AsyncHTTPTestCaseclassrepresents:1. AtestcasethatstartsupaFlaskHTTPServer.2. AtestcasethatstartsupaTornadoHTTPServer.3. Atestcasethatdoesn'tstartupanyHTTPServer.

5. IfwewanttoconvertthebytesinaJSONresponsebodytoaPythondictionary,wecanusethefollowingfunction:1. tornado.escape.json_decode2. tornado.escape.byte_decode3. tornado.escape.response_body_decode

Page 405: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

SummaryInthischapter,weunderstoodthedifferencebetweensynchronousandasynchronousexecution.WecreatedanewversionoftheRESTfulAPIthattakesadvantageofthenon-blockingfeaturesinTornadocombinedwithasynchronousexecution.WeimprovedscalabilityforourexistingAPIandwemadeitpossibletostartexecutingotherrequestswhilewaitingfortheslowI/Ooperationswithsensorsandactuators.Weavoidedsplittingourmethodsintomultiplemethodswithcallbacksbyusingthetornado.gengenerator-basedinterfacethatTornadoprovidestomakeiteasiertoworkinanasynchronousenvironment.

Then,wesetupatestingenvironment.Weinstallednose2tomakeiteasytodiscoverandexecuteunittests.Wewroteafirstroundofunittests,measuredtestcoverage,andthenwewroteadditionalunitteststoimprovetestcoverage.Wecreatedallthenecessaryteststohaveacompletecoverageofallthelinesofcode.

WebuiltRESTfulWebServiceswithDjango,Flask,andTornado.Wehavechosenthemostappropriateframeworkforeachcase.WelearnedtodesignaRESTfulAPIfromscratchandtorunallthenecessaryteststomakesurethatourAPIworkswithoutissuesaswereleasenewversions.Now,wearereadytocreateRESTfulAPIswithanyoftheWebframeworkswithwhomwehavebeenworkingthroughoutthisbook.

Page 406: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter11.ExerciseAnswers

Page 407: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter1,DevelopingRESTfulAPIswithDjango

Q1 2

Q2 1

Q3 3

Q4 1

Q5 3

Page 408: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter2,WorkingwithClass-BasedViewsandHyperlinkedAPIsinDjango

Q1 1

Q2 2

Q3 3

Q4 3

Q5 1

Page 409: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter3,ImprovingandAddingAuthenticationtoanAPIWithDjango

Q1 3

Q2 1

Q3 2

Q4 1

Q5 3

Page 410: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter4,Throttling,Filtering,Testing,andDeployinganAPIwithDjango

Q1 2

Q2 1

Q3 3

Q4 1

Q5 1

Page 411: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter5,DevelopingRESTfulAPIswithFlask

Q1 1

Q2 3

Q3 3

Q4 2

Q5 1

Page 412: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter6,WorkingwithModels,SQLAlchemy,andHyperlinkedAPIsinFlask

Q1 1

Q2 2

Q3 3

Q4 3

Q5 1

Page 413: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter7,ImprovingandAddingAuthenticationtoanAPIwithFlask

Q1 3

Q2 1

Q3 3

Q4 1

Q5 2

Page 414: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter8,TestingandDeployinganAPIwithFlask

Q1 1

Q2 2

Q3 1

Q4 1

Q5 3

Page 415: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter9,DevelopingRESTfulAPIswithTornado

Q1 2

Q2 1

Q3 3

Q4 3

Q5 2

Page 416: Building RESTful Python Web Servicesenglishonlineclub.com/pdf/Building RESTful Python...for developing RESTful Web Services. The book covers all the things you need to know to select

Chapter10,WorkingwithAsynchronousCode,Testing,andDeployinganAPIwithTornado

Q1 2

Q2 3

Q3 1

Q4 2

Q5 1