jeff 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard 'rspec', :cli => '-fd' do
2
2
  notification :off
3
3
  watch(%r{^spec/.+_spec\.rb$})
4
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
5
5
  watch('spec/spec_helper.rb') { 'spec' }
6
6
  end
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Jeff
2
2
 
3
3
  **Jeff** is a light-weight module that mixes in client behaviour for
4
- [Amazon Web Services (AWS)][aws]. It wraps [Excon][excon], parses with
5
- [Nokogiri][nokogiri], and implements [Signature Version 2][sign].
4
+ [Amazon Web Services (AWS)][aws]. It wraps [Excon][excon] and implements
5
+ [Signature Version 2][sign].
6
6
 
7
7
  ![jeff][jeff]
8
8
 
9
9
  ## Usage
10
10
 
11
- Build a a hypothetical client.
11
+ Build a hypothetical client.
12
12
 
13
13
  ```ruby
14
14
  class Client
@@ -20,15 +20,14 @@ Customise default headers and parameters.
20
20
 
21
21
  ```ruby
22
22
  class Client
23
- headers 'User-Agent' => 'Client'
24
- params 'Service' => 'SomeService',
23
+ params 'Service' => 'Service',
25
24
  'Tag' => -> { tag }
26
25
 
27
26
  attr_accessor :tag
28
27
  end
29
28
  ```
30
29
 
31
- Set an AWS endpoint and credentials.
30
+ Set AWS endpoint and credentials.
32
31
 
33
32
  ```ruby
34
33
  client = Client.new.tap do |config|
@@ -41,33 +40,16 @@ end
41
40
  You should now be able to access the endpoint.
42
41
 
43
42
  ```ruby
44
- res = client.post query: {},
45
- body: 'data'
46
-
47
- puts res.status # => 200
48
- puts res.body.root # => { 'Foo' => 'Bar' }
49
- ```
50
-
51
- ### Chunked Requests
52
-
53
- You can upload large files performantly by passing a proc that delivers
54
- chunks.
55
-
56
- ```ruby
57
- file = File.open 'data'
58
- chunker = -> { file.read Excon::CHUNK_SIZE).to_s }
59
-
60
- client.post query: {},
61
- request_block: chunker
43
+ client.post query: { 'Foo' => 'Bar' },
44
+ body: 'data'
62
45
  ```
63
46
 
64
47
  ## Compatibility
65
48
 
66
- **Jeff** is compatible with [all Ruby 1.9 flavours][travis].
49
+ **Jeff** is compatible with [Ruby 1.9 flavours][travis].
67
50
 
68
- [aws]: http://aws.amazon.com/
69
- [excon]: https://github.com/geemus/excon
70
- [jeff]: http://f.cl.ly/items/0a3R3J0k1R2f423k1q2l/jeff.jpg
71
- [nokogiri]: http://nokogiri.org/
72
- [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
73
- [travis]: http://travis-ci.org/#!/hakanensari/jeff
51
+ [aws]: http://aws.amazon.com/
52
+ [excon]: https://github.com/geemus/excon
53
+ [jeff]: http://f.cl.ly/items/0a3R3J0k1R2f423k1q2l/jeff.jpg
54
+ [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
55
+ [travis]: http://travis-ci.org/#!/hakanensari/jeff
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.email = ['hakan.ensari@papercavalier.com']
8
8
  gem.description = %q{Minimum-viable Amazon Web Services (AWS) client}
9
9
  gem.summary = %q{AWS client}
10
- gem.homepage = 'https://github.com/hakanensari/jeff'
10
+ gem.homepage = 'https://github.com/papercavalier/jeff'
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
13
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -16,8 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = Jeff::VERSION
18
18
 
19
- gem.add_dependency 'excon', '~> 0.14.0'
20
- gem.add_dependency 'nokogiri', '~> 1.5'
19
+ gem.add_dependency 'excon', '~> 0.15.0'
21
20
  gem.add_development_dependency 'guard-rspec'
22
21
  gem.add_development_dependency 'rake'
23
22
  gem.add_development_dependency 'rspec'
@@ -1,12 +1,14 @@
1
- require 'time'
2
-
3
1
  require 'excon'
2
+ require 'time'
4
3
 
5
4
  require 'jeff/secret'
6
- require 'jeff/streamer'
7
5
  require 'jeff/version'
8
6
 
7
+ # Jeff is a light-weight module that mixes in client behaviour for Amazon Web
8
+ # Services (AWS).
9
9
  module Jeff
10
+ USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
11
+
10
12
  MissingEndpoint = Class.new ArgumentError
11
13
  MissingKey = Class.new ArgumentError
12
14
  MissingSecret = Class.new ArgumentError
@@ -15,6 +17,13 @@ module Jeff
15
17
 
16
18
  def self.included(base)
17
19
  base.extend ClassMethods
20
+
21
+ base.headers 'User-Agent' => USER_AGENT
22
+
23
+ base.params 'AWSAccessKeyId' => -> { key },
24
+ 'SignatureVersion' => '2',
25
+ 'SignatureMethod' => 'HmacSHA256',
26
+ 'Timestamp' => -> { Time.now.utc.iso8601 }
18
27
  end
19
28
 
20
29
  # Internal: Builds a sorted query.
@@ -23,7 +32,7 @@ module Jeff
23
32
  #
24
33
  # Returns a query String.
25
34
  def build_query(hsh)
26
- default_params
35
+ params
27
36
  .merge(hsh)
28
37
  .sort
29
38
  .map { |k, v| "#{k}=#{ escape v }" }
@@ -32,22 +41,10 @@ module Jeff
32
41
 
33
42
  # Internal: Returns an Excon::Connection.
34
43
  def connection
35
- @connection ||= Excon.new endpoint, headers: default_headers,
44
+ @connection ||= Excon.new endpoint, headers: headers,
36
45
  idempotent: true
37
46
  end
38
47
 
39
- # Internal: Returns the Hash default request parameters.
40
- def default_params
41
- self.class.params.reduce({}) do |a, (k, v)|
42
- a.update k => (v.is_a?(Proc) ? instance_exec(&v) : v)
43
- end
44
- end
45
-
46
- # Internal: Returns the Hash default headers.
47
- def default_headers
48
- self.class.headers
49
- end
50
-
51
48
  # Internal: Gets the String AWS endpoint.
52
49
  #
53
50
  # Raises a MissingEndpoint error if endpoint is missing.
@@ -58,6 +55,11 @@ module Jeff
58
55
  # Sets the String AWS endpoint.
59
56
  attr_writer :endpoint
60
57
 
58
+ # Internal: Returns the Hash default headers.
59
+ def headers
60
+ self.class.headers
61
+ end
62
+
61
63
  # Internal: Gets the String AWS access key id.
62
64
  #
63
65
  # Raises a MissingKey error if key is missing.
@@ -68,6 +70,13 @@ module Jeff
68
70
  # Sets the String AWS access key id.
69
71
  attr_writer :key
70
72
 
73
+ # Internal: Returns the Hash default request parameters.
74
+ def params
75
+ self.class.params.reduce({}) do |a, (k, v)|
76
+ a.update k => (v.is_a?(Proc) ? instance_exec(&v) : v)
77
+ end
78
+ end
79
+
71
80
  # Internal: Gets the Jeff::Secret.
72
81
  #
73
82
  # Raises a MissingSecret error if secret is missing.
@@ -84,18 +93,13 @@ module Jeff
84
93
  @secret = Secret.new key
85
94
  end
86
95
 
87
- # Generate HTTP request verb methods that sign queries and then delegate
88
- # request to Excon.
89
- Excon::HTTP_VERBS. each do |method|
96
+ # Generate HTTP request verb methods that sign queries and return response
97
+ # bodies as IO objects.
98
+ Excon::HTTP_VERBS.each do |method|
90
99
  eval <<-DEF
91
100
  def #{method}(opts = {})
92
- streamer = Streamer.new
93
- opts.update method: :#{method},
94
- response_block: streamer
95
- res = connection.request sign opts
96
- res.body = streamer
97
-
98
- res
101
+ opts.update method: :#{method}
102
+ connection.request sign opts
99
103
  end
100
104
  DEF
101
105
  end
@@ -134,16 +138,13 @@ module Jeff
134
138
  end
135
139
 
136
140
  module ClassMethods
137
- # Amazon recommends that libraries identify themselves via a User Agent.
138
- USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
139
-
140
141
  # Gets/Updates the default headers.
141
142
  #
142
143
  # hsh - A Hash of headers.
143
144
  #
144
145
  # Returns the Hash headers.
145
146
  def headers(hsh = nil)
146
- @headers ||= { 'User-Agent' => USER_AGENT }
147
+ @headers ||= {}
147
148
  @headers.update hsh if hsh
148
149
 
149
150
  @headers
@@ -155,12 +156,7 @@ module Jeff
155
156
  #
156
157
  # Returns the Hash parameters.
157
158
  def params(hsh = nil)
158
- @params ||= {
159
- 'AWSAccessKeyId' => -> { key },
160
- 'SignatureVersion' => '2',
161
- 'SignatureMethod' => 'HmacSHA256',
162
- 'Timestamp' => -> { Time.now.utc.iso8601 }
163
- }
159
+ @params ||= {}
164
160
  @params.update hsh if hsh
165
161
 
166
162
  @params
@@ -1,3 +1,3 @@
1
1
  module Jeff
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -61,8 +61,8 @@ describe Jeff do
61
61
  client.key = 'key'
62
62
  end
63
63
 
64
- describe '#default_params' do
65
- subject { client.default_params }
64
+ describe '#params' do
65
+ subject { client.params }
66
66
 
67
67
  it 'should include the key' do
68
68
  subject['AWSAccessKeyId'].should eql client.key
@@ -116,11 +116,13 @@ describe Jeff do
116
116
 
117
117
  Excon::HTTP_VERBS.each do |method|
118
118
  describe "##{method}" do
119
- subject { client.send(method, mock: true).body.root['request'] }
119
+ subject do
120
+ client.send(method, mock: true).body
121
+ end
120
122
 
121
123
  before do
122
124
  Excon.stub({ method: method.to_sym }) do |params|
123
- { body: "<request>#{params[:method]}</request>" }
125
+ { body: method, status: 200 }
124
126
  end
125
127
  end
126
128
 
@@ -132,12 +134,12 @@ describe Jeff do
132
134
  end
133
135
  end
134
136
 
135
- context 'given a failed request' do
137
+ context 'given a temporary failure' do
136
138
  before do
137
139
  has_run = false
138
140
  Excon.stub({ method: :get }) do |params|
139
141
  if has_run
140
- { status: 200 }
142
+ { body: 'ok', status: 200 }
141
143
  else
142
144
  has_run = true
143
145
  raise Excon::Errors::SocketError.new Exception.new 'Mock Error'
@@ -148,21 +150,7 @@ describe Jeff do
148
150
  after { Excon.stubs.clear }
149
151
 
150
152
  it 'should retry' do
151
- client.get(mock: true).status.should be 200
152
- end
153
- end
154
-
155
- context 'given a malformed XML' do
156
- before do
157
- Excon.stub({ method: :get }) do |params|
158
- { body: "\n<?xml version=\"1.0\" ?><foo/>" }
159
- end
160
- end
161
-
162
- after { Excon.stubs.clear }
163
-
164
- it 'should not raise an error' do
165
- client.get mock: true
153
+ client.get(mock: true).body.should eq 'ok'
166
154
  end
167
155
  end
168
156
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jeff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-11 00:00:00.000000000 Z
12
+ date: 2012-07-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: excon
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.14.0
21
+ version: 0.15.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,23 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.14.0
30
- - !ruby/object:Gem::Dependency
31
- name: nokogiri
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: '1.5'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: '1.5'
29
+ version: 0.15.0
46
30
  - !ruby/object:Gem::Dependency
47
31
  name: guard-rspec
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -105,20 +89,14 @@ files:
105
89
  - LICENSE
106
90
  - README.md
107
91
  - Rakefile
108
- - examples/amazon.yml.example
109
- - examples/debug.rb
110
92
  - jeff.gemspec
111
93
  - lib/jeff.rb
112
- - lib/jeff/document.rb
113
94
  - lib/jeff/secret.rb
114
- - lib/jeff/streamer.rb
115
95
  - lib/jeff/version.rb
116
- - spec/jeff/document_spec.rb
117
96
  - spec/jeff/secret_spec.rb
118
- - spec/jeff/streamer_spec.rb
119
97
  - spec/jeff_spec.rb
120
98
  - spec/spec_helper.rb
121
- homepage: https://github.com/hakanensari/jeff
99
+ homepage: https://github.com/papercavalier/jeff
122
100
  licenses: []
123
101
  post_install_message:
124
102
  rdoc_options: []
@@ -130,12 +108,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
130
108
  - - ! '>='
131
109
  - !ruby/object:Gem::Version
132
110
  version: '0'
111
+ segments:
112
+ - 0
113
+ hash: -177229951203254918
133
114
  required_rubygems_version: !ruby/object:Gem::Requirement
134
115
  none: false
135
116
  requirements:
136
117
  - - ! '>='
137
118
  - !ruby/object:Gem::Version
138
119
  version: '0'
120
+ segments:
121
+ - 0
122
+ hash: -177229951203254918
139
123
  requirements: []
140
124
  rubyforge_project:
141
125
  rubygems_version: 1.8.23
@@ -143,9 +127,6 @@ signing_key:
143
127
  specification_version: 3
144
128
  summary: AWS client
145
129
  test_files:
146
- - spec/jeff/document_spec.rb
147
130
  - spec/jeff/secret_spec.rb
148
- - spec/jeff/streamer_spec.rb
149
131
  - spec/jeff_spec.rb
150
132
  - spec/spec_helper.rb
151
- has_rdoc:
@@ -1,3 +0,0 @@
1
- key: foo
2
- secret: bar
3
- tag: baz
@@ -1,41 +0,0 @@
1
- $:.unshift File.expand_path '../../lib', __FILE__
2
-
3
- require 'yaml'
4
-
5
- require 'jeff'
6
- require 'pry'
7
- require 'pry-doc'
8
-
9
- class Client
10
- include Jeff
11
-
12
- params 'AssociateTag' => -> { tag },
13
- 'Service' => 'AWSECommerceService',
14
- 'Version' => '2011-08-01'
15
-
16
- attr_accessor :tag
17
-
18
- def initialize
19
- self.key = config['key']
20
- self.secret = config['secret']
21
- self.tag = config['tag']
22
- self.endpoint = 'http://ecs.amazonaws.com/onca/xml'
23
- end
24
-
25
- def find(asins)
26
- params = {
27
- 'Operation' => 'ItemLookup',
28
- 'ItemId' => Array(asins).join(',')
29
- }
30
-
31
- get query: params
32
- end
33
-
34
- private
35
-
36
- def config
37
- @config ||= YAML.load_file File.expand_path '../amazon.yml', __FILE__
38
- end
39
- end
40
-
41
- binding.pry
@@ -1,48 +0,0 @@
1
- require 'nokogiri'
2
-
3
- module Jeff
4
- class Document < Nokogiri::XML::SAX::Document
5
- def characters(val)
6
- val.strip!
7
- unless val.empty?
8
- (node['__content__'] ||= '') << val
9
- end
10
- end
11
-
12
- def end_element(key)
13
- child = @stack.pop
14
-
15
- if child.keys == ['__content__']
16
- child = child['__content__']
17
- end
18
-
19
- case node[key]
20
- when Array
21
- node[key] << child
22
- when Hash, String
23
- node[key] = [node[key], child]
24
- else
25
- node[key] = child
26
- end
27
- end
28
-
29
- def start_element(key, attrs = [])
30
- @stack << {}
31
- attrs.each { |attr| node.store *attr }
32
- end
33
-
34
- def start_document
35
- @stack = [{}]
36
- end
37
-
38
- def root
39
- @stack.first
40
- end
41
-
42
- private
43
-
44
- def node
45
- @stack.last
46
- end
47
- end
48
- end
@@ -1,22 +0,0 @@
1
- require 'jeff/document'
2
-
3
- module Jeff
4
- class Streamer
5
- def initialize
6
- @parser = Nokogiri::XML::SAX::PushParser.new Document.new
7
- end
8
-
9
- def call(chunk, remaining_bytes, total_bytes)
10
- @parser << chunk.sub(/^\n/, '')
11
- @parser.finish if remaining_bytes == 0
12
- end
13
-
14
- def document
15
- @parser.document
16
- end
17
-
18
- def root
19
- document.root
20
- end
21
- end
22
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe Document do
5
- let(:io) do
6
- StringIO.new %{
7
- <?xml version=\"1.0\" ?>
8
- <ItemAttributes>
9
- <Title>Anti-Oedipus</Title>
10
- <Author>Gilles Deleuze</Author>
11
- <Author>Felix Guattari</Author>
12
- <Creator Role="Translator">Robert Hurley</Creator>
13
- </ItemAttributes>
14
- }.strip
15
- end
16
-
17
- let(:doc) { described_class.new }
18
- let(:parser) { Nokogiri::XML::SAX::Parser.new doc }
19
-
20
- before do
21
- io.rewind
22
- parser.parse io
23
- end
24
-
25
- describe '#root' do
26
- subject { doc.root['ItemAttributes'] }
27
-
28
- it { should be_a Hash }
29
-
30
- it 'should handle only children' do
31
- subject['Title'].should eql 'Anti-Oedipus'
32
- end
33
-
34
- it 'should hande arrays' do
35
- subject['Author'].should be_an Array
36
- end
37
-
38
- it 'should handle attributes' do
39
- creator = subject['Creator']
40
- creator['Role'].should eql 'Translator'
41
- creator['__content__'].should eql 'Robert Hurley'
42
- end
43
-
44
- it 'should ignore space between tags' do
45
- should_not have_key '__content__'
46
- end
47
- end
48
- end
49
- end
@@ -1,28 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe Streamer do
5
- let(:streamer) { Streamer.new }
6
-
7
- let(:xml) do
8
- %{
9
- <?xml version="1.0" ?>
10
- <foo>
11
- <bar>1</bar>
12
- </foo>
13
- }.strip.gsub />\s+</, '><'
14
- end
15
-
16
- it 'should parse a stream' do
17
- bytes_sent = 0
18
- total_bytes = xml.size
19
-
20
- xml.scan(/.{1,8}/m).each do |chunk|
21
- bytes_sent += chunk.size
22
- streamer.call chunk, total_bytes - bytes_sent, total_bytes
23
- end
24
-
25
- streamer.root.should have_key 'foo'
26
- end
27
- end
28
- end