charlesju-aws-simpledb 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ Manifest
3
+ README
4
+ Rakefile
5
+ aws-simpledb.gemspec
6
+ lib/aws-simpledb.rb
7
+ lib/aws-simpledb/error.rb
8
+ lib/aws-simpledb/service.rb
9
+ spec/aws-simpledb/service_spec.rb
10
+ spec/spec_helper.rb
data/README CHANGED
@@ -1,5 +1,10 @@
1
+ == About
1
2
  Amazon SimpleDB
2
-
3
3
  Reworking this API to be up-to-date with the new APIs.
4
4
 
5
+ == Commands
6
+ sudo gem build aws-simpledb.gemspec
7
+ sudo gem install aws-simpledb-0.1.0.gem
8
+
9
+ == Notes
5
10
  THIS GEM IS NOT READY FOR USE
data/Rakefile CHANGED
@@ -1,20 +1,14 @@
1
1
  require 'rubygems'
2
- require 'spec/rake/spectask'
3
- require 'rake/gempackagetask'
4
-
5
- Spec::Rake::SpecTask.new
6
-
7
- gem_spec = eval(IO.read(File.join(File.dirname(__FILE__), "aws-simpledb.gemspec")))
8
-
9
- desc "Open an irb session preloaded with this library"
10
- task :console do
11
- sh "irb -rubygems -I lib -r aws-simpledb.rb"
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('aws-simpledb', '0.1.3') do |p|
6
+ p.description = "This is an easy way to use Amazon SimpleDB"
7
+ p.url = "http://github.com/charlesju/aws-simpledb"
8
+ p.author = "Charles Ju"
9
+ p.email = "charlesju@gmail.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.development_dependencies = []
12
12
  end
13
13
 
14
- Rake::GemPackageTask.new(gem_spec) do |pkg|
15
- pkg.gem_spec = gem_spec
16
- end
17
-
18
- task :install => [:package] do
19
- sh %{sudo gem install pkg/#{gem_spec.name}-#{gem_spec.version}}
20
- end
14
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each {|ext| load ext}
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{aws-simpledb}
5
+ s.version = "0.1.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Charles Ju"]
9
+ s.date = %q{2009-09-16}
10
+ s.description = %q{This is an easy way to use Amazon SimpleDB}
11
+ s.email = %q{charlesju@gmail.com}
12
+ s.extra_rdoc_files = ["LICENSE", "README", "lib/aws-simpledb.rb", "lib/aws-simpledb/error.rb", "lib/aws-simpledb/service.rb"]
13
+ s.files = ["LICENSE", "Manifest", "README", "Rakefile", "aws-simpledb.gemspec", "lib/aws-simpledb.rb", "lib/aws-simpledb/error.rb", "lib/aws-simpledb/service.rb", "spec/aws-simpledb/service_spec.rb", "spec/spec_helper.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/charlesju/aws-simpledb}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Aws-simpledb", "--main", "README"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{aws-simpledb}
19
+ s.rubygems_version = %q{1.3.2}
20
+ s.summary = %q{This is an easy way to use Amazon SimpleDB}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ require 'aws-simpledb/error'
2
+ require 'aws-simpledb/service'
3
+
@@ -0,0 +1,42 @@
1
+ module AwsSdb
2
+
3
+ class Error < RuntimeError ; end
4
+
5
+ class RequestError < Error
6
+ attr_reader :request_id
7
+
8
+ def initialize(message, request_id=nil)
9
+ super(message)
10
+ @request_id = request_id
11
+ end
12
+ end
13
+
14
+ class InvalidDomainNameError < RequestError ; end
15
+ class InvalidParameterValueError < RequestError ; end
16
+ class InvalidNextTokenError < RequestError ; end
17
+ class InvalidNumberPredicatesError < RequestError ; end
18
+ class InvalidNumberValueTestsError < RequestError ; end
19
+ class InvalidQueryExpressionError < RequestError ; end
20
+ class MissingParameterError < RequestError ; end
21
+ class NoSuchDomainError < RequestError ; end
22
+ class NumberDomainsExceededError < RequestError ; end
23
+ class NumberDomainAttributesExceededError < RequestError ; end
24
+ class NumberDomainBytesExceededError < RequestError ; end
25
+ class NumberItemAttributesExceededError < RequestError ; end
26
+ class RequestTimeoutError < RequestError ; end
27
+
28
+ class FeatureDeprecatedError < RequestError ; end
29
+
30
+ class ConnectionError < Error
31
+ attr_reader :response
32
+
33
+ def initialize(response)
34
+ super(
35
+ "#{response.code} \
36
+ #{response.message if response.respond_to?(:message)}"
37
+ )
38
+ @response = response
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,214 @@
1
+ require 'logger'
2
+ require 'time'
3
+ require 'cgi'
4
+ require 'uri'
5
+ require 'net/http'
6
+ require 'base64'
7
+ require 'openssl'
8
+ require 'rexml/document'
9
+ require 'rexml/xpath'
10
+
11
+ module AwsSdb
12
+
13
+ class Service
14
+ def initialize(options={})
15
+ @access_key_id = options[:access_key_id] || ENV['AMAZON_ACCESS_KEY_ID']
16
+ @secret_access_key = options[:secret_access_key] || ENV['AMAZON_SECRET_ACCESS_KEY']
17
+ @base_url = options[:url] || 'http://sdb.amazonaws.com'
18
+ @logger = options[:logger] || Logger.new("aws-simpledb.log")
19
+ end
20
+
21
+ def list_domains(max = nil, token = nil)
22
+ params = { 'Action' => 'ListDomains' }
23
+ params['NextToken'] =
24
+ token unless token.nil? || token.empty?
25
+ params['MaxNumberOfDomains'] =
26
+ max.to_s unless max.nil? || max.to_i == 0
27
+ doc = call(:get, params)
28
+ results = []
29
+ REXML::XPath.each(doc, '//DomainName/text()') do |domain|
30
+ results << domain.to_s
31
+ end
32
+ return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
33
+ end
34
+
35
+ def create_domain(domain)
36
+ call(:post, { 'Action' => 'CreateDomain', 'DomainName'=> domain.to_s })
37
+ nil
38
+ end
39
+
40
+ def delete_domain(domain)
41
+ call(
42
+ :delete,
43
+ { 'Action' => 'DeleteDomain', 'DomainName' => domain.to_s }
44
+ )
45
+ nil
46
+ end
47
+ # <QueryWithAttributesResult><Item><Name>in-c2ffrw</Name><Attribute><Name>code</Name><Value>in-c2ffrw</Value></Attribute><Attribute><Name>date_created</Name><Value>2008-10-31</Value></Attribute></Item><Item>
48
+ def query_with_attributes(domain, query, max = nil, token = nil)
49
+ params = {
50
+ 'Action' => 'QueryWithAttributes',
51
+ 'QueryExpression' => query,
52
+ 'DomainName' => domain.to_s
53
+ }
54
+ params['NextToken'] =
55
+ token unless token.nil? || token.empty?
56
+ params['MaxNumberOfItems'] =
57
+ max.to_s unless max.nil? || max.to_i == 0
58
+
59
+ doc = call(:get, params)
60
+ results = []
61
+ REXML::XPath.each(doc, "//Item") do |item|
62
+ name = REXML::XPath.first(item, './Name/text()').to_s
63
+
64
+
65
+ attributes = {'Name' => name}
66
+ REXML::XPath.each(item, "./Attribute") do |attr|
67
+ key = REXML::XPath.first(attr, './Name/text()').to_s
68
+ value = REXML::XPath.first(attr, './Value/text()').to_s
69
+ ( attributes[key] ||= [] ) << value
70
+ end
71
+ results << attributes
72
+ end
73
+ return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
74
+ end
75
+
76
+ # <QueryResult><ItemName>in-c2ffrw</ItemName><ItemName>in-72yagt</ItemName><ItemName>in-52j8gj</ItemName>
77
+ def query(domain, query, max = nil, token = nil)
78
+ params = {
79
+ 'Action' => 'Query',
80
+ 'QueryExpression' => query,
81
+ 'DomainName' => domain.to_s
82
+ }
83
+ params['NextToken'] =
84
+ token unless token.nil? || token.empty?
85
+ params['MaxNumberOfItems'] =
86
+ max.to_s unless max.nil? || max.to_i == 0
87
+
88
+ doc = call(:get, params)
89
+ results = []
90
+ REXML::XPath.each(doc, '//ItemName/text()') do |item|
91
+ results << item.to_s
92
+ end
93
+ return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
94
+ end
95
+
96
+ def get_select(query, max = nil, token = nil)
97
+ params = {
98
+ 'Action' => 'Select',
99
+ 'SelectExpression' => query
100
+ }
101
+ params['NextToken'] =
102
+ token unless token.nil? || token.empty?
103
+ params['MaxNumberOfItems'] =
104
+ max.to_s unless max.nil? || max.to_i == 0
105
+
106
+ doc = call(:get, params)
107
+ results = []
108
+ REXML::XPath.each(doc, "//Item") do |item|
109
+ name = REXML::XPath.first(item, './Name/text()').to_s
110
+ attributes = {'Name' => name}
111
+ REXML::XPath.each(item, "./Attribute") do |attr|
112
+ key = REXML::XPath.first(attr, './Name/text()').to_s
113
+ value = REXML::XPath.first(attr, './Value/text()').to_s
114
+ ( attributes[key] ||= [] ) << value
115
+ end
116
+ results << attributes
117
+ end
118
+ return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
119
+ end
120
+
121
+ def put_attributes(domain, item, attributes, replace = true)
122
+ params = {
123
+ 'Action' => 'PutAttributes',
124
+ 'DomainName' => domain.to_s,
125
+ 'ItemName' => item.to_s
126
+ }
127
+ count = 0
128
+ attributes.each do | key, values |
129
+ ([]<<values).flatten.each do |value|
130
+ params["Attribute.#{count}.Name"] = key.to_s
131
+ params["Attribute.#{count}.Value"] = value.to_s
132
+ params["Attribute.#{count}.Replace"] = replace
133
+ count += 1
134
+ end
135
+ end
136
+ call(:put, params)
137
+ nil
138
+ end
139
+
140
+ def get_attributes(domain, item)
141
+ doc = call(
142
+ :get,
143
+ {
144
+ 'Action' => 'GetAttributes',
145
+ 'DomainName' => domain.to_s,
146
+ 'ItemName' => item.to_s
147
+ }
148
+ )
149
+ attributes = {}
150
+ REXML::XPath.each(doc, "//Attribute") do |attr|
151
+ key = REXML::XPath.first(attr, './Name/text()').to_s
152
+ value = REXML::XPath.first(attr, './Value/text()').to_s
153
+ ( attributes[key] ||= [] ) << value
154
+ end
155
+ attributes
156
+ end
157
+
158
+ def delete_attributes(domain, item)
159
+ call(
160
+ :delete,
161
+ {
162
+ 'Action' => 'DeleteAttributes',
163
+ 'DomainName' => domain.to_s,
164
+ 'ItemName' => item.to_s
165
+ }
166
+ )
167
+ nil
168
+ end
169
+
170
+ protected
171
+
172
+ def call(method, params)
173
+ params.merge!( {
174
+ 'Version' => '2007-11-07',
175
+ 'SignatureVersion' => '1',
176
+ 'AWSAccessKeyId' => @access_key_id,
177
+ 'Timestamp' => Time.now.gmtime.iso8601
178
+ }
179
+ )
180
+ data = ''
181
+ query = []
182
+ params.keys.sort_by { |k| k.upcase }.each do |key|
183
+ data << "#{key}#{params[key].to_s}"
184
+ query << "#{key}=#{CGI::escape(params[key].to_s)}"
185
+ end
186
+ digest = OpenSSL::Digest::Digest.new('sha1')
187
+ hmac = OpenSSL::HMAC.digest(digest, @secret_access_key, data)
188
+ signature = Base64.encode64(hmac).strip
189
+ query << "Signature=#{CGI::escape(signature)}"
190
+ query = query.join('&')
191
+ url = "#{@base_url}?#{query}"
192
+ uri = URI.parse(url)
193
+ @logger.debug("#{url}") if @logger
194
+ response =
195
+ Net::HTTP.new(uri.host, uri.port).send_request(method, uri.request_uri)
196
+ @logger.debug("#{response.code}\n#{response.body}") if @logger
197
+ raise(ConnectionError.new(response)) unless (200..400).include?(
198
+ response.code.to_i
199
+ )
200
+ doc = REXML::Document.new(response.body)
201
+ error = doc.get_elements('*/Errors/Error')[0]
202
+ raise(
203
+ Module.class_eval(
204
+ "AwsSdb::#{error.get_elements('Code')[0].text}Error"
205
+ ).new(
206
+ error.get_elements('Message')[0].text,
207
+ doc.get_elements('*/RequestID')[0].text
208
+ )
209
+ ) unless error.nil?
210
+ doc
211
+ end
212
+ end
213
+
214
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'digest/sha1'
4
+ require 'net/http'
5
+ require 'rexml/document'
6
+
7
+ require 'rubygems'
8
+ require 'uuidtools'
9
+
10
+ include AwsSdb
11
+
12
+ describe Service, "when creating a new domain" do
13
+ before(:all) do
14
+ @service = AwsSdb::Service.new
15
+ @domain = "test-#{UUID.random_create.to_s}"
16
+ domains = @service.list_domains[0]
17
+ domains.each do |d|
18
+ @service.delete_domain(d) if d =~ /^test/
19
+ end
20
+ end
21
+
22
+ after(:all) do
23
+ @service.delete_domain(@domain)
24
+ end
25
+
26
+ it "should not raise an error if a valid new domain name is given" do
27
+ lambda {
28
+ @service.create_domain("test-#{UUID.random_create.to_s}")
29
+ }.should_not raise_error
30
+ end
31
+
32
+ it "should not raise an error if the domain name already exists" do
33
+ domain = "test-#{UUID.random_create.to_s}"
34
+ lambda {
35
+ @service.create_domain(domain)
36
+ @service.create_domain(domain)
37
+ }.should_not raise_error
38
+ end
39
+
40
+ it "should raise an error if an a nil or '' domain name is given" do
41
+ lambda {
42
+ @service.create_domain('')
43
+ }.should raise_error(InvalidParameterValueError)
44
+ lambda {
45
+ @service.create_domain(nil)
46
+ }.should raise_error(InvalidParameterValueError)
47
+ lambda {
48
+ @service.create_domain(' ')
49
+ }.should raise_error(InvalidParameterValueError)
50
+ end
51
+
52
+ it "should raise an error if the domain name length is < 3 or > 255" do
53
+ lambda {
54
+ @service.create_domain('xx')
55
+ }.should raise_error(InvalidParameterValueError)
56
+ lambda {
57
+ @service.create_domain('x'*256)
58
+ }.should raise_error(InvalidParameterValueError)
59
+ end
60
+
61
+ it "should only accept domain names with a-z, A-Z, 0-9, '_', '-', and '.' " do
62
+ lambda {
63
+ @service.create_domain('@$^*()')
64
+ }.should raise_error(InvalidParameterValueError)
65
+ end
66
+
67
+ it "should only accept a maximum of 100 domain names"
68
+
69
+ it "should not have to call amazon to determine domain name correctness"
70
+ end
71
+
72
+ describe Service, "when listing domains" do
73
+ before(:all) do
74
+ @service = AwsSdb::Service.new
75
+ @domain = "test-#{UUID.random_create.to_s}"
76
+ @service.list_domains[0].each do |d|
77
+ @service.delete_domain(d) if d =~ /^test/
78
+ end
79
+ @service.create_domain(@domain)
80
+ end
81
+
82
+ after(:all) do
83
+ @service.delete_domain(@domain)
84
+ end
85
+
86
+ it "should return a complete list" do
87
+ result = nil
88
+ lambda { result = @service.list_domains[0] }.should_not raise_error
89
+ result.should_not be_nil
90
+ result.should_not be_empty
91
+ result.include?(@domain).should == true
92
+ end
93
+ end
94
+
95
+ describe Service, "when deleting domains" do
96
+ before(:all) do
97
+ @service = AwsSdb::Service.new
98
+ @domain = "test-#{UUID.random_create.to_s}"
99
+ @service.list_domains[0].each do |d|
100
+ @service.delete_domain(d) if d =~ /^test/
101
+ end
102
+ @service.create_domain(@domain)
103
+ end
104
+
105
+ after do
106
+ @service.delete_domain(@domain)
107
+ end
108
+
109
+ it "should be able to delete an existing domain" do
110
+ lambda { @service.delete_domain(@domain) }.should_not raise_error
111
+ end
112
+
113
+ it "should not raise an error trying to delete a non-existing domain" do
114
+ lambda {
115
+ @service.delete_domain(UUID.random_create.to_s)
116
+ }.should_not raise_error
117
+ end
118
+ end
119
+
120
+ describe Service, "when managing items" do
121
+ before(:all) do
122
+ @service = AwsSdb::Service.new
123
+ @domain = "test-#{UUID.random_create.to_s}"
124
+ @service.list_domains[0].each do |d|
125
+ @service.delete_domain(d) if d =~ /^test/
126
+ end
127
+ @service.create_domain(@domain)
128
+ @item = "test-#{UUID.random_create.to_s}"
129
+ @attributes = {
130
+ :question => 'What is the answer?',
131
+ :answer => [ true, 'testing123', 4.2, 42, 420 ]
132
+ }
133
+ end
134
+
135
+ after(:all) do
136
+ @service.delete_domain(@domain)
137
+ end
138
+
139
+ it "should be able to put attributes" do
140
+ lambda {
141
+ @service.put_attributes(@domain, @item, @attributes)
142
+ }.should_not raise_error
143
+ end
144
+
145
+ it "should be able to get attributes" do
146
+ result = nil
147
+ lambda {
148
+ result = @service.get_attributes(@domain, @item)
149
+ }.should_not raise_error
150
+ result.should_not be_nil
151
+ result.should_not be_empty
152
+ result.has_key?('answer').should == true
153
+ @attributes[:answer].each do |v|
154
+ result['answer'].include?(v.to_s).should == true
155
+ end
156
+ end
157
+
158
+ it "should be able to query" do
159
+ result = nil
160
+ lambda {
161
+ result = @service.query(@domain, "[ 'answer' = '42' ]")[0]
162
+ }.should_not raise_error
163
+ result.should_not be_nil
164
+ result.should_not be_empty
165
+ result.should_not be_nil
166
+ result.include?(@item).should == true
167
+ end
168
+
169
+ it "should be able to query with attributes"
170
+
171
+ it "should be able to delete attributes" do
172
+ lambda {
173
+ @service.delete_attributes(@domain, @item)
174
+ }.should_not raise_error
175
+ end
176
+ end
177
+
178
+ # TODO Pull the specs from the amazon docs and write more rspec
179
+ # 100 attributes per each call
180
+ # 256 total attribute name-value pairs per item
181
+ # 250 million attributes per domain
182
+ # 10 GB of total user data storage per domain
183
+ # ...etc...
@@ -0,0 +1,4 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'aws-simpledb'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: charlesju-aws-simpledb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Ju
@@ -9,19 +9,10 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-16 00:00:00 -07:00
12
+ date: 2009-09-16 00:00:00 -07:00
13
13
  default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: uuidtools
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
24
- version:
14
+ dependencies: []
15
+
25
16
  description: This is an easy way to use Amazon SimpleDB
26
17
  email: charlesju@gmail.com
27
18
  executables: []
@@ -29,18 +20,33 @@ executables: []
29
20
  extensions: []
30
21
 
31
22
  extra_rdoc_files:
32
- - README
33
23
  - LICENSE
24
+ - README
25
+ - lib/aws-simpledb.rb
26
+ - lib/aws-simpledb/error.rb
27
+ - lib/aws-simpledb/service.rb
34
28
  files:
35
29
  - LICENSE
30
+ - Manifest
36
31
  - README
37
32
  - Rakefile
33
+ - aws-simpledb.gemspec
34
+ - lib/aws-simpledb.rb
35
+ - lib/aws-simpledb/error.rb
36
+ - lib/aws-simpledb/service.rb
37
+ - spec/aws-simpledb/service_spec.rb
38
+ - spec/spec_helper.rb
38
39
  has_rdoc: true
39
40
  homepage: http://github.com/charlesju/aws-simpledb
40
41
  licenses:
41
42
  post_install_message:
42
- rdoc_options: []
43
-
43
+ rdoc_options:
44
+ - --line-numbers
45
+ - --inline-source
46
+ - --title
47
+ - Aws-simpledb
48
+ - --main
49
+ - README
44
50
  require_paths:
45
51
  - lib
46
52
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -53,14 +59,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
59
  requirements:
54
60
  - - ">="
55
61
  - !ruby/object:Gem::Version
56
- version: "0"
62
+ version: "1.2"
57
63
  version:
58
64
  requirements: []
59
65
 
60
66
  rubyforge_project: aws-simpledb
61
67
  rubygems_version: 1.3.5
62
68
  signing_key:
63
- specification_version: 2
64
- summary: Amazon SimpleDB
69
+ specification_version: 3
70
+ summary: This is an easy way to use Amazon SimpleDB
65
71
  test_files: []
66
72