Retro - Web Services Made Easy - Presentation Transcript
Retro
web services made easy
Sébastien Pierre, Datalicious
@Montréal Python 7, May. 2009
www.datalicious.ca | github.com/sebastien/retro
Why another
Framework
Toolkit ?
Things have Changed
The 90s
“ Golden Spinning Logo Era”
Web = Pages + Animated GIFs
3
Things have Changed
The 00s
“ Social Startup AJAX 2.0 Era”
Web = Model +View + Controller
4
Things have Changed
Now
“ The Web of Data Era*”
* aka “ semantic linked RDF data 3.0” ;)
Web = Data + Interfaces
5
Shifting our Perspective
Web services are
the new interfaces
for the “ web of data”
6
On Retro : Key Concepts
Web services are APIs exposed over HTTP
Web Service = HTTP data bus
7
Retro : An example
Population GDP Land Area (hectares)
British Columbia 4,113,487 $164,583.00 91,837,000
Yukon 30,372 $1,452.00 47,890,000
Alberta 3,290,350 $187,493.00 61,400,000
Northwest Territories 41,464 $4,138.00 112,984,000
Exposing data through
Saskatchewan 968,157 $39,834.00 58,939,000
Manitoba 1,148,401 $41,662.00 54,029,000
Nunavut 29,474 $1,115.00 192,138,000
Ontario
Quebec
A web service 12,160,282
7,546,131
$536,340.00
$265,888.00
88,303,000
132,970,000
New Brunswick 729,997 $23,669.00 7,133,000
Prince Edward Island 135,851 $4,149 564,000
Nova Scotia 913,462 $28,803.00 5,277,000
Newfoundland and Labrador 505,469 $19,696.00 35,498,000
8
Step 1: Getting the data
;\"Population\";\"GDP\";\"Land Area (hectares)\"
\"British Columbia\";\"4,113,487\";\"$164,583.00\";\"91,837,000\"
\"Yukon\";\"30,372\";\"$1,452.00\";\"47,890,000\"
\"Alberta\";\"3,290,350\";\"$187,493.00\";\"61,400,000\"
\"Northwest Territories\";\"41,464\";\"$4,138.00\";\"112,984,000\"
\"Saskatchewan\";\"968,157\";\"$39,834.00\";\"58,939,000\"
\"Manitoba\";\"1,148,401\";\"$41,662.00\";\"54,029,000\"
\"Nunavut\";\"29,474\";\"$1,115.00\";\"192,138,000\"
\"Ontario\";\"12,160,282\";\"$536,340.00\";\"88,303,000\"
\"Quebec\";\"7,546,131\";\"$265,888.00\";\"132,970,000\"
\"New Brunswick\";\"729,997\";\"$23,669.00\";\"7,133,000\"
\"Prince Edward Island\";\"135,851\";\"$4,149\";\"564,000\"
\"Nova Scotia\";\"913,462\";\"$28,803.00\";\"5,277,000\"
\"Newfoundland and Labrador\";\"505,469\";\"$19,696.00\";\"35,498,000\"
DATA = list(csv.reader(open(\"provinces.csv\"),delimiter=\";\"))[1:]
9
Step 2: Writing the API
class ProvincesAPI:
def __init__( self, data ):
self.data = data
def list( self ):
return list(l[0] for l in self.data)
def province( self, name ):
return ([l for l in self.data if l[0] == name] or [None])[0]
def population( self, province ):
return self.province(province)[1]
def gdp( self, province ):
return self.province(province)[2]
def landArea( self, province ):
return self.province(province)[3]
10
Step 3: Testing it
if __name__ == \"__main__\":
api = ProvincesAPI(DATA)
for p in api.list():
print \"Province:\", p
print \" gdp :\", api.gdp(p)
print \" population :\", api.population(p)
print \" land area :\", api.landArea(p)
Province: British Columbia Province: Saskatchewan Province: New Brunswick
gdp : $164,583.00 gdp : $39,834.00 gdp : $23,669.00
population : 4,113,487 population : 968,157 population : 729,997
land area : 91,837,000 land area : 58,939,000 land area : 7,133,000
Province: Yukon Province: Manitoba Province: Prince Edward Island
gdp : $1,452.00
gdp : $41,662.00 gdp : $4,149
population : 30,372
land area : 47,890,000 population : 1,148,401 population : 135,851
Province: Alberta land area : 54,029,000 land area : 564,000
gdp : $187,493.00 Province: Nunavut Province: Nova Scotia
population : 3,290,350 gdp : $1,115.00 gdp : $28,803.00
land area : 61,400,000 population : 29,474 population : 913,462
Province: Northwest Territories land area : 192,138,000 land area : 5,277,000
gdp : $4,138.00 Province: Ontario Province: Newfoundland and Labrador
population : 41,464 gdp : $536,340.00 gdp : $19,696.00
land area : 112,984,000 population : 12,160,282 population : 505,469
11
Step 4 : Exposing it
from retro import *
class ProvincesAPI(Component):
def __init__( self, data ):
Component.__init__(self)
self.data = data
@expose(GET=\"/api/provinces/all\")
def list( self ):
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
12
Step 4 : Exposing it
Import retro
from retro import *
class ProvincesAPI(Component):
def __init__( self, data ):
Component.__init__(self)
self.data = data
@expose(GET=\"/api/provinces/all\")
def list( self ):
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
13
Step 4 : Exposing it
from retro import *
class ProvincesAPI(Component):
Decorate the def __init__( self, data ):
method to
Component.__init__(self)
expose it over HTTP
self.data = data
@expose(GET=\"/api/provinces/all\")
def list( self ):
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
14
Step 4 : Exposing it
from retro import *
class ProvincesAPI(Component):
Use HTTP methodsself, data ):
def __init__(
as keyword Component.__init__(self)
arguments
self.data = data
@expose(GET=\"/api/provinces/all\")
def list( self ):
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
15
Step 4 : Exposing it
from retro import *
class ProvincesAPI(Component):
def __init__( self, data ):
Specify URL to respond to
Component.__init__(self)
self.data = data
@expose(GET=\"/api/provinces/all\")
def list( self ):
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
16
Step 4 : Exposing it
from retro import *
class ProvincesAPI(Component):
def __init__( self, data ):
Component.__init__(self)
self.data = data
@expose(GET=\"/api/provinces/all\")Pass parameters from URL
def list( self ): to Python method
return list(l[0] for l in self.data)
@expose(GET=\"/api/province/{name:string}\")
def province( self, name ):
name = name.lower().replace(\" \",\"\")
return ([l for l in self.data if l[0].lower().replace(\" \",\"\") == name] or [None])[0]
17
Step 4 : Exposing it
@expose(GET=\"/api/province/{province:string}/population\")
def population( self, province ):
return self.province(province)[1]
@expose(GET=\"/api/province/{province:string}/gdp\")
def gdp( self, province ):
return self.province(province)[2]
@expose(GET=\"/api/province/{province:string}/landArea\")
def landArea( self, province ):
return self.province(province)[3]
Don't change a line in your
existing code
18
Step 5 : Running it
if __name__ == \"__main__\":
run(components=[ProvincesAPI(DATA)],port=8000)
curl localhost:8000/api/provinces/all
[\"British Columbia\",\"Yukon\",\"Alberta\",\"Northwest
Territories\",\"Saskatchewan\",\"Manitoba\",\"Nunavut\",\"Ontario\",\"Quebec\",\"New Brunswick\",
\"Prince Edward Island\",\"Nova Scotia\",\"Newfoundland and Labrador\"]
curl localhost:8000/api/province/quebec/gdp
\"$265,888.00\"
curl localhost:8000/api/province/quebec/population
\"7,546,131\"
curl localhost:8000/api/province/quebec/landArea
\"132,970,000\"
19
Step 5 : Running it
if __name__ == \"__main__\":
run(components=[ProvincesAPI(DATA)],port=8000)
Runs the Retro embedded
curl localhost:8000/api/provinces/all
web server
[\"British Columbia\",\"Yukon\",\"Alberta\",\"Northwest
Territories\",\"Saskatchewan\",\"Manitoba\",\"Nunavut\",\"Ontario\",\"Quebec\",\"New Brunswick\",
\"Prince Edward Island\",\"Nova Scotia\",\"Newfoundland and Labrador\"]
curl localhost:8000/api/province/quebec/gdp
\"$265,888.00\"
curl localhost:8000/api/province/quebec/population
\"7,546,131\"
curl localhost:8000/api/province/quebec/landArea
\"132,970,000\"
20
Step 5 : Running it
if __name__ == \"__main__\":
run(components=[ProvincesAPI(DATA)],port=8000)
curl localhost:8000/api/provinces/all
[\"British Columbia\",\"Yukon\",\"Alberta\",\"Northwest
Territories\",\"Saskatchewan\",\"Manitoba\",\"Nunavut\",\"Ontario\",\"Quebec\",\"New Brunswick\",
\"Prince Edward Island\",\"Nova Scotia\",\"Newfoundland and Labrador\"]
curl localhost:8000/api/province/quebec/gdp
\"$265,888.00\" Data is returned as
JSON
curl localhost:8000/api/province/quebec/population
\"7,546,131\"
curl localhost:8000/api/province/quebec/landArea
\"132,970,000\"
21
Step 6 : Surprise !
if __name__ == \"__main__\":
api = ProvincesAPI(DATA)
for p in api.list():
print \"Province:\", p
print \" gdp :\", api.gdp(p)
print \" population :\", api.population(p)
print \" land area :\", api.landArea(p)
run(components=[api],port=8000)
Your API object remains usable as it was
before in Python
22
Retro in a Nutshell
WSGIbased
Works with CGI, FCGI, SCGI, WSGI on Apache, Lighttpd, Nginx
HTTP Streaming
Uses WSGI generator (yield) support
Standalone
Comes with embedded web server and event reactor
Lightweight
Minimal API, small footprint
23
The end
Thank you !
www.github.com/sebastien/retro
www.datalicious.ca
sebastien@datalicious.ca
0 comments
Post a comment