api_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE +22 -0
  5. data/README.md +13 -0
  6. data/Rakefile +1 -0
  7. data/api_client.gemspec +24 -0
  8. data/examples/digg.rb +31 -0
  9. data/examples/flickr.rb +37 -0
  10. data/examples/github.rb +52 -0
  11. data/examples/highrise.rb +41 -0
  12. data/examples/twitter.rb +34 -0
  13. data/examples/twitter_oauth.rb +36 -0
  14. data/lib/api_client.rb +45 -0
  15. data/lib/api_client/base.rb +52 -0
  16. data/lib/api_client/connection/abstract.rb +73 -0
  17. data/lib/api_client/connection/basic.rb +105 -0
  18. data/lib/api_client/connection/middlewares/request/logger.rb +17 -0
  19. data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
  20. data/lib/api_client/connection/oauth.rb +18 -0
  21. data/lib/api_client/errors.rb +16 -0
  22. data/lib/api_client/mixins/configuration.rb +24 -0
  23. data/lib/api_client/mixins/connection_hooks.rb +24 -0
  24. data/lib/api_client/mixins/delegation.rb +23 -0
  25. data/lib/api_client/mixins/inheritance.rb +19 -0
  26. data/lib/api_client/mixins/instantiation.rb +35 -0
  27. data/lib/api_client/mixins/scoping.rb +49 -0
  28. data/lib/api_client/resource/base.rb +63 -0
  29. data/lib/api_client/resource/scope.rb +73 -0
  30. data/lib/api_client/scope.rb +101 -0
  31. data/lib/api_client/utils.rb +18 -0
  32. data/lib/api_client/version.rb +3 -0
  33. data/spec/api_client/base/connection_hook_spec.rb +18 -0
  34. data/spec/api_client/base/delegation_spec.rb +15 -0
  35. data/spec/api_client/base/inheritance_spec.rb +44 -0
  36. data/spec/api_client/base/instantiation_spec.rb +54 -0
  37. data/spec/api_client/base/parsing_spec.rb +36 -0
  38. data/spec/api_client/base/scoping_spec.rb +60 -0
  39. data/spec/api_client/base_spec.rb +17 -0
  40. data/spec/api_client/connection/abstract_spec.rb +21 -0
  41. data/spec/api_client/connection/basic_spec.rb +135 -0
  42. data/spec/api_client/connection/oauth_spec.rb +27 -0
  43. data/spec/api_client/connection/request/logger_spec.rb +19 -0
  44. data/spec/api_client/connection/request/oauth_spec.rb +26 -0
  45. data/spec/api_client/resource/base_spec.rb +78 -0
  46. data/spec/api_client/resource/scope_spec.rb +96 -0
  47. data/spec/api_client/scope_spec.rb +170 -0
  48. data/spec/api_client/utils_spec.rb +32 -0
  49. data/spec/spec_helper.rb +13 -0
  50. data/spec/support/fake_logger.rb +15 -0
  51. data/spec/support/matchers.rb +5 -0
  52. metadata +148 -0
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ bin
6
+ coverage
7
+ examples/config.rb
8
+ *.sw[a-z]
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fs-api.gemspec
4
+ gemspec
5
+
6
+ # Testing
7
+ gem "rspec"
8
+ gem "simplecov"
9
+
10
+ # Soft dependencies
11
+ gem "simple_oauth"
12
+ gem 'multi_xml'
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'
@@ -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
@@ -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
@@ -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
@@ -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