api_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/Rakefile +1 -0
- data/api_client.gemspec +24 -0
- data/examples/digg.rb +31 -0
- data/examples/flickr.rb +37 -0
- data/examples/github.rb +52 -0
- data/examples/highrise.rb +41 -0
- data/examples/twitter.rb +34 -0
- data/examples/twitter_oauth.rb +36 -0
- data/lib/api_client.rb +45 -0
- data/lib/api_client/base.rb +52 -0
- data/lib/api_client/connection/abstract.rb +73 -0
- data/lib/api_client/connection/basic.rb +105 -0
- data/lib/api_client/connection/middlewares/request/logger.rb +17 -0
- data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
- data/lib/api_client/connection/oauth.rb +18 -0
- data/lib/api_client/errors.rb +16 -0
- data/lib/api_client/mixins/configuration.rb +24 -0
- data/lib/api_client/mixins/connection_hooks.rb +24 -0
- data/lib/api_client/mixins/delegation.rb +23 -0
- data/lib/api_client/mixins/inheritance.rb +19 -0
- data/lib/api_client/mixins/instantiation.rb +35 -0
- data/lib/api_client/mixins/scoping.rb +49 -0
- data/lib/api_client/resource/base.rb +63 -0
- data/lib/api_client/resource/scope.rb +73 -0
- data/lib/api_client/scope.rb +101 -0
- data/lib/api_client/utils.rb +18 -0
- data/lib/api_client/version.rb +3 -0
- data/spec/api_client/base/connection_hook_spec.rb +18 -0
- data/spec/api_client/base/delegation_spec.rb +15 -0
- data/spec/api_client/base/inheritance_spec.rb +44 -0
- data/spec/api_client/base/instantiation_spec.rb +54 -0
- data/spec/api_client/base/parsing_spec.rb +36 -0
- data/spec/api_client/base/scoping_spec.rb +60 -0
- data/spec/api_client/base_spec.rb +17 -0
- data/spec/api_client/connection/abstract_spec.rb +21 -0
- data/spec/api_client/connection/basic_spec.rb +135 -0
- data/spec/api_client/connection/oauth_spec.rb +27 -0
- data/spec/api_client/connection/request/logger_spec.rb +19 -0
- data/spec/api_client/connection/request/oauth_spec.rb +26 -0
- data/spec/api_client/resource/base_spec.rb +78 -0
- data/spec/api_client/resource/scope_spec.rb +96 -0
- data/spec/api_client/scope_spec.rb +170 -0
- data/spec/api_client/utils_spec.rb +32 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/fake_logger.rb +15 -0
- data/spec/support/matchers.rb +5 -0
- metadata +148 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Marcin Bunsch
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
ApiClient
|
2
|
+
=========
|
3
|
+
|
4
|
+
ApiClient is an experimental builder for HTTP API clients. The goal is to provide a easy to use engine for constructing queries and map the responses to instances of Hashie::Mash subclasses.
|
5
|
+
|
6
|
+
Basically you should be able to build a client for any HTTP based API, without the need for handling connections, parsing responses or instantiating objects. All you need to do is choose where the request should go and enhance your client classes. See the examples dir for usage examples.
|
7
|
+
|
8
|
+
Current state is alpha - it works, but the query interface is not final and is subject to change at any time. Hell, even the can even change without prior notice. You were warned.
|
9
|
+
|
10
|
+
Copyright
|
11
|
+
---------
|
12
|
+
|
13
|
+
Copyright (c) 2011 Marcin Bunsch, See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/api_client.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "api_client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "api_client"
|
7
|
+
s.version = ApiClient::VERSION
|
8
|
+
s.authors = ["Marcin Bunsch"]
|
9
|
+
s.email = ["marcin@futuresimple.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{API client builder}
|
12
|
+
s.description = %q{API client builder}
|
13
|
+
|
14
|
+
s.rubyforge_project = "api_client"
|
15
|
+
|
16
|
+
s.add_dependency 'faraday', "=0.7.5"
|
17
|
+
s.add_dependency 'hashie', "=1.2.0"
|
18
|
+
s.add_dependency 'yajl-ruby'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
data/examples/digg.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "api_client"
|
4
|
+
|
5
|
+
module Digg
|
6
|
+
|
7
|
+
class Base < ApiClient::Base
|
8
|
+
|
9
|
+
always do
|
10
|
+
endpoint "http://services.digg.com"
|
11
|
+
params :type => 'json'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class Collection < Base
|
17
|
+
|
18
|
+
def self.diggs
|
19
|
+
Digg.build get('/2.0/digg.getAll')['diggs']
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class Digg < Base
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Digg::Collection.diggs.each do |digg|
|
30
|
+
puts "#{digg.user.name}: #{digg.item.title}"
|
31
|
+
end
|
data/examples/flickr.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "./examples/config" if File.exists?('examples/config.rb')
|
4
|
+
require "api_client"
|
5
|
+
|
6
|
+
module Flickr
|
7
|
+
|
8
|
+
class Base < ApiClient::Base
|
9
|
+
always do
|
10
|
+
endpoint "http://api.flickr.com"
|
11
|
+
params :api_key => FLICKR_API_KEY,
|
12
|
+
:format => 'json',
|
13
|
+
:nojsoncallback => 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Collection < Base
|
18
|
+
|
19
|
+
def self.interesting
|
20
|
+
build params(:method => 'flickr.interestingness.getList').
|
21
|
+
get("/services/rest")
|
22
|
+
end
|
23
|
+
|
24
|
+
def photos
|
25
|
+
Photo.build self['photos']['photo']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class Photo < Base
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
Flickr::Collection.interesting.photos.each do |photo|
|
36
|
+
puts photo.title
|
37
|
+
end
|
data/examples/github.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "./examples/config" if File.exists?('examples/config.rb')
|
4
|
+
require "api_client"
|
5
|
+
require "time"
|
6
|
+
|
7
|
+
module Github
|
8
|
+
|
9
|
+
class Base < ApiClient::Base
|
10
|
+
namespace false
|
11
|
+
|
12
|
+
always do
|
13
|
+
endpoint "https://api.github.com"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class User < Base
|
18
|
+
|
19
|
+
def self.find(name)
|
20
|
+
fetch("/users/#{name}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def events
|
24
|
+
Github::Event.fetch("/users/#{login}/events")
|
25
|
+
end
|
26
|
+
|
27
|
+
def received_events
|
28
|
+
Github::Event.fetch("/users/#{login}/received_events")
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class Event < Base
|
34
|
+
|
35
|
+
def created_at
|
36
|
+
Time.parse self['created_at']
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
user = Github::User.find("marcinbunsch")
|
44
|
+
|
45
|
+
user.events.each do |event|
|
46
|
+
case event.type
|
47
|
+
when "FollowEvent"
|
48
|
+
puts "#{event.created_at} #{event.payload.target.login}: #{event.type}"
|
49
|
+
else
|
50
|
+
puts "#{event.created_at} #{event.repo.name} : #{event.type}"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "./examples/config" if File.exists?('examples/config.rb')
|
4
|
+
require "api_client"
|
5
|
+
require "multi_xml"
|
6
|
+
|
7
|
+
module Highrise
|
8
|
+
|
9
|
+
class Base < ApiClient::Resource::Base
|
10
|
+
format :xml
|
11
|
+
|
12
|
+
# In this hook we set the basic auth provided in the options
|
13
|
+
connection do |connection|
|
14
|
+
connection.handler.basic_auth connection.options[:user], connection.options[:pass]
|
15
|
+
end
|
16
|
+
|
17
|
+
always do
|
18
|
+
endpoint HIGHRISE_URL
|
19
|
+
options(:user => HIGHRISE_TOKEN, :pass => 'X')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class Person < Base
|
25
|
+
namespace false
|
26
|
+
|
27
|
+
always do
|
28
|
+
path "people"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build_one(hash)
|
32
|
+
hash.has_key?('people') ? build_many(hash['people']) : super(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
Highrise::Person.find_all.each do |person|
|
40
|
+
puts "#{person.first_name} #{person.last_name}"
|
41
|
+
end
|
data/examples/twitter.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "api_client"
|
4
|
+
|
5
|
+
module Twitter
|
6
|
+
|
7
|
+
class Base < ApiClient::Base
|
8
|
+
always do
|
9
|
+
endpoint "http://api.twitter.com/"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Tweet < Base
|
14
|
+
end
|
15
|
+
|
16
|
+
class User < Base
|
17
|
+
|
18
|
+
def self.find_by_username(name)
|
19
|
+
params(:screen_name => name).fetch("/1/users/show.json")
|
20
|
+
end
|
21
|
+
|
22
|
+
def tweets
|
23
|
+
Tweet.params(:screen_name => self.screen_name).fetch("/1/statuses/user_timeline.json")
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
user = Twitter::User.find_by_username("marcinbunsch")
|
31
|
+
puts user.name
|
32
|
+
user.tweets.each do |tweet|
|
33
|
+
puts " #{tweet.text}"
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "./examples/config" if File.exists?('examples/config.rb')
|
4
|
+
require "api_client"
|
5
|
+
|
6
|
+
module TwitterOauth
|
7
|
+
|
8
|
+
class Base < ApiClient::Base
|
9
|
+
|
10
|
+
always do
|
11
|
+
endpoint "https://api.twitter.com/"
|
12
|
+
adapter :oauth
|
13
|
+
|
14
|
+
options :oauth => {
|
15
|
+
:consumer_key => TWITTER_CONSUMER_KEY, :consumer_secret => TWITTER_CONSUMER_SECRET
|
16
|
+
}
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Tweet < Base
|
23
|
+
|
24
|
+
def self.tweet(message)
|
25
|
+
build post('/1/statuses/update.json', :status => message)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
config = { :token => TWITTER_TOKEN, :token_secret => TWITTER_SECRET }
|
33
|
+
|
34
|
+
message = TwitterOauth::Tweet.options(:oauth => config).tweet("test #{Time.now.to_i}")
|
35
|
+
|
36
|
+
puts message.text
|
data/lib/api_client.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "api_client/version"
|
2
|
+
module ApiClient
|
3
|
+
class << self
|
4
|
+
attr_accessor :logger
|
5
|
+
end
|
6
|
+
|
7
|
+
autoload :Base, "api_client/base"
|
8
|
+
autoload :Errors, "api_client/errors"
|
9
|
+
autoload :Scope, "api_client/scope"
|
10
|
+
autoload :Utils, "api_client/utils"
|
11
|
+
|
12
|
+
module Mixins
|
13
|
+
autoload :ConnectionHooks, "api_client/mixins/connection_hooks"
|
14
|
+
autoload :Delegation, "api_client/mixins/delegation"
|
15
|
+
autoload :Configuration, "api_client/mixins/configuration"
|
16
|
+
autoload :Inheritance, "api_client/mixins/inheritance"
|
17
|
+
autoload :Instantiation, "api_client/mixins/instantiation"
|
18
|
+
autoload :Scoping, "api_client/mixins/scoping"
|
19
|
+
end
|
20
|
+
|
21
|
+
module Resource
|
22
|
+
autoload :Base, "api_client/resource/base"
|
23
|
+
autoload :Scope, "api_client/resource/scope"
|
24
|
+
end
|
25
|
+
|
26
|
+
module Connection
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_accessor :default
|
30
|
+
end
|
31
|
+
self.default = :basic
|
32
|
+
|
33
|
+
module Middlewares
|
34
|
+
module Request
|
35
|
+
autoload :OAuth, "api_client/connection/middlewares/request/oauth"
|
36
|
+
autoload :Logger, "api_client/connection/middlewares/request/logger"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
autoload :Abstract, "api_client/connection/abstract"
|
41
|
+
autoload :Basic, "api_client/connection/basic"
|
42
|
+
autoload :Oauth, "api_client/connection/oauth"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "hashie"
|
2
|
+
require "yajl"
|
3
|
+
|
4
|
+
module ApiClient
|
5
|
+
|
6
|
+
class Base < Hashie::Mash
|
7
|
+
|
8
|
+
extend ApiClient::Mixins::Inheritance
|
9
|
+
extend ApiClient::Mixins::Instantiation
|
10
|
+
extend ApiClient::Mixins::Scoping
|
11
|
+
extend ApiClient::Mixins::ConnectionHooks
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend ApiClient::Mixins::Delegation
|
15
|
+
extend ApiClient::Mixins::Configuration
|
16
|
+
|
17
|
+
delegate :fetch, :get, :put, :post, :delete, :headers, :endpoint, :options, :adapter, :params, :to => :scope
|
18
|
+
|
19
|
+
dsl_accessor :format, :namespace
|
20
|
+
|
21
|
+
def subkey_class
|
22
|
+
Hashie::Mash
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(response)
|
26
|
+
response = response.body if response.is_a?(Faraday::Response)
|
27
|
+
if self.format == :json
|
28
|
+
Yajl::Parser.parse(response)
|
29
|
+
elsif self.format == :xml
|
30
|
+
MultiXml.parse(response)
|
31
|
+
else
|
32
|
+
response
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defaults
|
39
|
+
self.format :json
|
40
|
+
|
41
|
+
def id
|
42
|
+
self['id']
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
"#<#{self.class} id: #{self.id}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Faraday for making requests
|
2
|
+
require 'faraday'
|
3
|
+
|
4
|
+
module ApiClient
|
5
|
+
|
6
|
+
module Connection
|
7
|
+
|
8
|
+
class Abstract
|
9
|
+
|
10
|
+
attr_accessor :endpoint, :handler, :options
|
11
|
+
|
12
|
+
def initialize(endpoint, options = {})
|
13
|
+
raise "Cannot instantiate abstract class" if self.class == ApiClient::Connection::Abstract
|
14
|
+
@endpoint = endpoint
|
15
|
+
@options = options
|
16
|
+
create_handler
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_handler
|
20
|
+
end
|
21
|
+
|
22
|
+
#### ApiClient::Connection::Abstract#get
|
23
|
+
# Performs a GET request
|
24
|
+
# Accepts three parameters:
|
25
|
+
#
|
26
|
+
# * path - the path the request should go to
|
27
|
+
# * data - (optional) the query, passed as a hash and converted into query params
|
28
|
+
# * headers - (optional) headers sent along with the request
|
29
|
+
#
|
30
|
+
def get(path, data = {}, headers = {})
|
31
|
+
end
|
32
|
+
|
33
|
+
#### ApiClient::Connection::Abstract#post
|
34
|
+
# Performs a POST request
|
35
|
+
# Accepts three parameters:
|
36
|
+
#
|
37
|
+
# * path - the path request should go to
|
38
|
+
# * data - (optional) data sent in the request
|
39
|
+
# * headers - (optional) headers sent along in the request
|
40
|
+
#
|
41
|
+
def post(path, data = {}, headers = {})
|
42
|
+
end
|
43
|
+
|
44
|
+
#### ApiClient::Connection::Abstract#put
|
45
|
+
# Performs a PUT request
|
46
|
+
# Accepts three parameters:
|
47
|
+
#
|
48
|
+
# * path - the path request should go to
|
49
|
+
# * data - (optional) data sent in the request
|
50
|
+
# * headers - (optional) headers sent along in the request
|
51
|
+
#
|
52
|
+
def put(path, data = {}, headers = {})
|
53
|
+
end
|
54
|
+
|
55
|
+
#### FS::Connection#delete
|
56
|
+
# Performs a DELETE request
|
57
|
+
# Accepts three parameters:
|
58
|
+
#
|
59
|
+
# * path - the path request should go to
|
60
|
+
# * data - (optional) the query, passed as a hash and converted into query params
|
61
|
+
# * headers - (optional) headers sent along in the request
|
62
|
+
#
|
63
|
+
def delete(path, data = {}, headers = {})
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
"#<#{self.class} endpoint: \"#{endpoint}\">"
|
68
|
+
end
|
69
|
+
alias :to_s :inspect
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|