JSON AND THE ARGONAUTS API WYNNNETHERLAND
Jan 28, 2015
JSON AND THE ARGONAUTSAPI
WYNNNETHERLAND
whoami
I write API wrappersA lot of API wrappers!
& more!
Why create API wrappers?
After all, we have
curl
curl http://api.twitter.com/1/users/show.json?screen_name=pengwynn
Net::HTTP
url = "http://api.twitter.com/1/users/show.json?screen_name=pengwynn"Net::HTTP.get(URI.parse(url))
Because we're Rubyists, and we want
Idiomatic access
{"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
{"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
Rubyified keys
{"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
{"chart":{ "issue_date":2006-03-04, "description":"Chart", "chart_items":{ "first_position":1, "total_returned":15, "total_records":25663, "chart_item":[{ "song_name":"Lonely Runs Both Ways", "artist_name":"Alison Krauss + Union Station", "peek":1, "catalog_no":"610525", "rank":1, "exrank":1, "weeks_on":65, "album_id":655684, ... }}
... and method names
# Retrieve the details about a user by email# # +email+ (Required)# The email of the user to look within. To run getInfoByEmail on multiple addresses, simply pass a comma-separated list of valid email addresses.# def self.info_by_email(email) email = email.join(',') if email.is_a?(Array) Mash.new(self.get('/', ! ! :query => {! ! ! :method => 'user.getInfoByEmail', ! ! ! :email => email }.merge(Upcoming.default_options))).rsp.userend
# Retrieve the details about a user by email# # +email+ (Required)# The email of the user to look within. To run getInfoByEmail on multiple addresses, simply pass a comma-separated list of valid email addresses.# def self.info_by_email(email) email = email.join(',') if email.is_a?(Array) Mash.new(self.get('/', ! ! :query => {! ! ! :method => 'user.getInfoByEmail', ! ! ! :email => email }.merge(Upcoming.default_options))).rsp.userend
More Ruby like than
SYNTACTIC SUGAR
Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
Method chaining
stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
Method chaining
client.statuses.update.json! :status => 'this status is from grackle'
client.statuses.update.json! :status => 'this status is from grackle'
Method chaining Bang for POST
APPROACHES
Simple Wrapping
SOME TWITTER EXAMPLESTwitter Auth from @mbleighuser.twitter.post('/statuses/update.json', 'status' => 'Tweet, tweet!'
)
Grackle from @hayesdavisclient.statuses.update.json! :status => 'Tweet, tweet!'
Twitter from @jnunemaker client.update('Tweet, tweet!')
Wrapping
Wrapping... with style
Abstraction
Why simply wrap?
Insulate against change
Leverage API documentation
Why abstract?
...or writing APIs to wrap APIs
Simplify a complex API
Provide a business domain
TOOLS
Transports
Net::HTTP
Patronhttp://toland.github.com/patron/
Typhoeushttp://github.com/pauldix/typhoeus
em-http-requesthttp://github.com/igrigorik/em-http-request
Parsers
Crack
Puts the party in HTTParty
http://github.com/jnunemaker/crack
JSON
yajl-rubyhttp://github.com/brianmario/yajl-ruby
multi_jsonhttp://github.com/intridea/multi_json
Higher-level libraries
HTTParty
When you HTTParty, you must party hard!
http://github.com/jnunemaker/httparty
HTTParty- Ruby module - GET, POST, PUT, DELETE - basic_auth, base_uri, default_params, etc.
- Net::HTTP for transport- Crack parses JSON and XML
HTTPartyclass Delicious include HTTParty base_uri 'https://api.del.icio.us/v1' def initialize(u, p) @auth = {:username => u, :password => p} end
...
def recent(options={}) options.merge!({:basic_auth => @auth}) self.class.get('/posts/recent', options) end
...
monster_mashhttp://github.com/dbalatero/monster_mash
HTTParty for Typhoeus, a monster. Get it?
RestClienthttp://github.com/adamwiggins/rest-client
RestClient- Simple DSL- ActiveResource support- Built-in shell
RestClient.post(! 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' })
Wearyhttp://github.com/mwunsch/weary
Weary- Simple DSL- Specify required, optional params- Async support
Wearydeclare "foo" do |r| r.url = "path/to/foo" r.via = :post r.requires = [:id, :bar] r.with = [:blah] r.authenticates = false r.follows = false r.headers = {'Accept' => 'text/html'}end
client.foo :id => 123, :bar => 'baz'
becomes
RackClienthttp://github.com/halorgium/rack-client
RackClient- Rack API- Middleware!
client = Rack::Client.new('http://whoismyrepresentative.com')
Faradayhttp://github.com/technoweenie/faraday
Faraday- Rack-like- Middleware!- Adapters
Faradayurl = 'http://api.twitter.com/1'conn = Faraday::Connection.new(:url => url ) do |builder| builder.adapter Faraday.default_adapter builder.use Faraday::Response::MultiJson builder.use Faraday::Response::Mashifyend
resp = conn.get do |req| req.url '/users/show.json', :screen_name => 'pengwynn'end
u = resp.bodyu.name# => "Wynn Netherland"
Faraday Middlewarehttp://github.com/pengwynn/faraday-middleware
Faraday Middleware- Hashie- Multi JSON- OAuth, OAuth2 as needed
My current stack- Faraday- Faraday Middleware - Hashie - Multi JSON- OAuth, OAuth2 as needed
Hashie- Mash- Dash- Trash- Clash
HTTPScoop
Charles Proxy
If you have an iOS app, you have an API ;-)
Testing
Fakewebhttp://github.com/chrisk/fakeweb
VCRhttp://github.com/myronmarston/vcr
Artifice
Artifice.activate_with(rack_endpoint) do # make some requests using Net::HTTPend
a @wycats joint
http://github.com/wycats/artifice
ShamRackhttp://github.com/mdub/sham_rack
ShamRackShamRack.at("sinatra.xyz").sinatra do get "/hello/:subject" do "Hello, #{params[:subject]}" endend
open("http://sinatra.xyz/hello/stranger").read
#=> "Hello, stranger"
ShamRackShamRack.at("rackup.xyz").rackup do use Some::Middleware use Some::Other::Middleware run MyApp.newend
Rack 'em up!
Authentication
Basic
OAuth2http://github.com/intridea/oauth2