ruby-aaws 0.6.0 → 0.7.0

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/ruby -w
2
2
  #
3
- # $Id: example1,v 1.4 2008/04/28 10:24:56 ianmacd Exp $
3
+ # $Id: example1,v 1.5 2009/06/02 01:09:51 ianmacd Exp $
4
4
 
5
5
  require 'amazon/aws'
6
6
  require 'amazon/aws/search'
@@ -44,7 +44,7 @@ response = request.search( is, rg, :ALL_PAGES ) do |page|
44
44
  # end
45
45
  # end
46
46
  # end
47
- printf( "Page %d had unique request ID %s.\n",
47
+ printf( "Page %d had unique request ID %s.\n",
48
48
  page_nr += 1,
49
49
  page.item_search_response[0].operation_request[0].request_id )
50
50
  printf( "Page %d contained %d result(s).\n",
@@ -56,7 +56,7 @@ end
56
56
  #
57
57
  nr_items = 0
58
58
  response.each do |page|
59
- page.item_search_response[0].items.each do |item_set|
59
+ page.item_search_response[0].items.each do |item_set|
60
60
  nr_items += item_set.item.size
61
61
  end
62
62
  end
@@ -1,4 +1,4 @@
1
- # $Id: amazon.rb,v 1.28 2009/05/26 15:25:20 ianmacd Exp $
1
+ # $Id: amazon.rb,v 1.29 2009/06/08 15:20:11 ianmacd Exp $
2
2
  #
3
3
 
4
4
  module Amazon
@@ -26,26 +26,28 @@ module Amazon
26
26
  # Shamelessly plagiarised from Wakou Aoyama's cgi.rb, but then altered
27
27
  # slightly to please AWS.
28
28
  #
29
- string.gsub( /([^a-zA-Z0-9_.~-]+)/n ) do
30
- '%' + $1.unpack( 'H2' * $1.size ).join( '%' ).upcase
29
+ string.gsub( /([^a-zA-Z0-9_.~-]+)/ ) do
30
+ '%' + $1.unpack( 'H2' * $1.bytesize ).join( '%' ).upcase
31
31
  end
32
32
  end
33
33
 
34
34
 
35
35
  # Convert a string from CamelCase to ruby_case.
36
36
  #
37
- def Amazon.uncamelise(str)
37
+ def Amazon.uncamelise(string)
38
38
  # Avoid modifying by reference.
39
39
  #
40
- str = str.dup
40
+ string = string.dup
41
41
 
42
42
  # Don't mess with string if all caps.
43
43
  #
44
- str.gsub!( /(.+?)(([A-Z][a-z]|[A-Z]+$))/, "\\1_\\2" ) if str =~ /[a-z]/
44
+ if string =~ /[a-z]/
45
+ string.gsub!( /(.+?)(([A-Z][a-z]|[A-Z]+$))/, "\\1_\\2" )
46
+ end
45
47
 
46
48
  # Convert to lower case.
47
49
  #
48
- str.downcase
50
+ string.downcase
49
51
  end
50
52
 
51
53
 
@@ -1,4 +1,4 @@
1
- # $Id: aws.rb,v 1.97 2009/05/26 15:24:31 ianmacd Exp $
1
+ # $Id: aws.rb,v 1.118 2009/06/15 23:51:53 ianmacd Exp $
2
2
  #
3
3
  #:include: ../../README.rdoc
4
4
 
@@ -8,12 +8,13 @@ module Amazon
8
8
 
9
9
  require 'amazon'
10
10
  require 'amazon/aws/cache'
11
+ require 'enumerator'
11
12
  require 'iconv'
12
13
  require 'rexml/document'
13
14
  require 'uri'
14
15
 
15
16
  NAME = '%s/%s' % [ Amazon::NAME, 'AWS' ]
16
- VERSION = '0.6.0'
17
+ VERSION = '0.7.0'
17
18
  USER_AGENT = '%s %s' % [ NAME, VERSION ]
18
19
 
19
20
  # Default Associate tags to use per locale.
@@ -121,7 +122,10 @@ module Amazon
121
122
  # If so, sign the request for authentication.
122
123
  #
123
124
  if request.config['secret_key_id']
124
- request.sign
125
+ unless request.sign
126
+ Amazon.dprintf( 'Warning! Failed to sign request. No OpenSSL support for SHA256 digest.' )
127
+ end
128
+
125
129
  url = ENDPOINT[request.locale].path + request.query
126
130
  end
127
131
 
@@ -252,7 +256,7 @@ module Amazon
252
256
  # dynamically defined by a separate process.
253
257
  #
254
258
  def AWSObject.yaml_load(io)
255
- io.each do |line|
259
+ io.each do |line|
256
260
 
257
261
  # File data is external, so it's deemed unsafe when $SAFE > 0, which
258
262
  # is the case with mod_ruby, for example, where $SAFE == 1.
@@ -269,7 +273,7 @@ module Amazon
269
273
 
270
274
  # Module#const_defined? takes 2 parameters in Ruby 1.9.
271
275
  #
272
- cl_name << false if Object.method( :const_defined? ).arity == -1
276
+ cl_name << false if RUBY_VERSION >= '1.9.0'
273
277
 
274
278
  unless AWSObject.const_defined?( *cl_name )
275
279
  AWSObject.const_set( m[1], Class.new( AWSObject ) )
@@ -299,6 +303,9 @@ module Amazon
299
303
  iv = '@' + method.id2name
300
304
 
301
305
  if instance_variables.include?( iv )
306
+
307
+ # Return the instance variable that matches the method called.
308
+ #
302
309
  instance_variable_get( iv )
303
310
  elsif instance_variables.include?( iv.to_sym )
304
311
 
@@ -306,6 +313,12 @@ module Amazon
306
313
  # not String.
307
314
  #
308
315
  instance_variable_get( iv.to_sym )
316
+ elsif @__val__.respond_to?( method.id2name )
317
+
318
+ # If our value responds to the method in question, call the method
319
+ # on that.
320
+ #
321
+ @__val__.send( method.id2name )
309
322
  else
310
323
  nil
311
324
  end
@@ -355,13 +368,8 @@ module Amazon
355
368
  alias :to_str :to_s
356
369
 
357
370
 
358
- def to_i # :nodoc:
359
- @__val__.to_i
360
- end
361
-
362
-
363
371
  def ==(other) # :nodoc:
364
- @__val__.to_s == other
372
+ @__val__.to_s == other
365
373
  end
366
374
 
367
375
 
@@ -456,7 +464,7 @@ module Amazon
456
464
 
457
465
  # Module#const_defined? takes 2 parameters in Ruby 1.9.
458
466
  #
459
- cl_name << false if Object.method( :const_defined? ).arity == -1
467
+ cl_name << false if RUBY_VERSION >= '1.9.0'
460
468
 
461
469
  # Create a class for the new element type unless it already exists.
462
470
  #
@@ -503,12 +511,12 @@ module Amazon
503
511
  #
504
512
  def get(discount=nil)
505
513
  if self.class.to_s =~ /Image$/ && @url
506
- url = URI.parse( @url[0] )
507
- url.path.sub!( /(\.\d\d\._)/, "\\1PE#{discount}" ) if discount
514
+ url = URI.parse( @url[0] )
515
+ url.path.sub!( /(\.\d\d\._)/, "\\1PE#{discount}" ) if discount
508
516
 
509
517
  # FIXME: All HTTP in Ruby/AWS should go through the same method.
510
518
  #
511
- Net::HTTP.start( url.host, url.port ) do |http|
519
+ Net::HTTP.start( url.host, url.port ) do |http|
512
520
  http.get( url.path )
513
521
  end.body
514
522
 
@@ -589,28 +597,35 @@ module Amazon
589
597
  OPERATIONS = %w[
590
598
  BrowseNodeLookup CustomerContentLookup CustomerContentSearch
591
599
  Help ItemLookup ItemSearch
592
- ListLookup ListSearch SellerListingLookup
593
- SellerListingSearch SellerLookup SimilarityLookup
594
- TagLookup TransactionLookup VehiclePartLookup
595
- VehiclePartSearch VehicleSearch
600
+ ListLookup ListSearch MultipleOperation
601
+ SellerListingLookup SellerListingSearch SellerLookup
602
+ SimilarityLookup TagLookup TransactionLookup
603
+ VehiclePartLookup VehiclePartSearch VehicleSearch
596
604
 
597
605
  CartAdd CartClear CartCreate
598
606
  CartGet CartModify
599
607
  ]
600
608
 
601
609
  attr_reader :kind
602
- attr_accessor :params
610
+ attr_accessor :params, :response_group
603
611
 
604
612
  def initialize(parameters)
605
613
 
606
614
  op_kind = self.class.to_s.sub( /^.*::/, '' )
607
- unless OPERATIONS.include?( op_kind ) || op_kind == 'MultipleOperation'
615
+ unless OPERATIONS.include?( op_kind )
608
616
  raise "Bad operation: #{op_kind}"
609
617
  end
610
618
  #raise 'Too many parameters' if parameters.size > 10
611
619
 
612
620
  @kind = op_kind
613
621
  @params = { 'Operation' => op_kind }.merge( parameters )
622
+
623
+ if ResponseGroup::DEFAULT.key?( op_kind )
624
+ @response_group =
625
+ ResponseGroup.new( ResponseGroup::DEFAULT[op_kind] )
626
+ else
627
+ @response_group = nil
628
+ end
614
629
  end
615
630
 
616
631
 
@@ -625,10 +640,12 @@ module Amazon
625
640
  #
626
641
  # is = ItemSearch.new( 'Books', { 'Title' => 'ruby programming' } )
627
642
  # is2 = ItemSearch.new( 'Music', { 'Artist' => 'stranglers' } )
628
- # is.batch( is2 )
643
+ # is.response_group = ResponseGroup.new( :Small )
644
+ # is2.response_group = ResponseGroup.new( :Tracks )
645
+ # is.batch( is2 )
629
646
  #
630
- # Please see MultipleOperation.new for details of a couple of
631
- # restrictions that also apply to batched operations.
647
+ # Please see MultipleOperation.new for implementation details that also
648
+ # apply to batched operations.
632
649
  #
633
650
  def batch(*operations)
634
651
 
@@ -650,6 +667,7 @@ module Amazon
650
667
  # Apply batch syntax.
651
668
  #
652
669
  @params = batch_parameters( @params, op.params )
670
+ @response_group.params = batch_response_groups( op )
653
671
  end
654
672
 
655
673
  # Reinstate the Operation parameter.
@@ -665,28 +683,33 @@ module Amazon
665
683
  @index ||= 1
666
684
 
667
685
  unless b_params.empty?
668
- op_str = self.class.to_s.sub( /^.+::/, '' )
686
+ op_str = @kind || self.class.to_s.sub( /^.*::/, '' )
669
687
 
670
688
  # Fudge the operation string if we're dealing with a shopping cart.
671
689
  #
672
690
  op_str = 'Item' if op_str =~ /^Cart/
673
691
 
674
- all_parameters = b_params
692
+ all_parameters = []
675
693
 
676
694
  # Shopping carts pass an empty hash in params, so we have to ditch
677
695
  # params in such a case to prevent the batch index from being off by
678
696
  # one.
679
697
  #
680
698
  all_parameters.concat( [ params ] ) unless params.empty?
699
+ all_parameters.concat( b_params )
681
700
 
682
701
  params = {}
702
+ index = 0
703
+
704
+ all_parameters.each do |hash|
683
705
 
684
- all_parameters.each_with_index do |hash, index|
706
+ next if hash.empty?
685
707
 
686
708
  # Don't batch an already batched hash.
687
709
  #
688
- if ! hash.empty? && hash.to_a[0][0] =~ /^.+\..+\..+$/
689
- params = hash
710
+ if hash.to_a[0][0] =~ /^.+\..+\..+$/
711
+ params.merge!( hash )
712
+ @index += 1
690
713
  next
691
714
  end
692
715
 
@@ -694,6 +717,8 @@ module Amazon
694
717
  shared_param = '%s.%d.%s' % [ op_str, @index + index, tag ]
695
718
  params[shared_param] = val
696
719
  end
720
+
721
+ index += 1
697
722
  end
698
723
 
699
724
  @index += b_params.size
@@ -706,40 +731,87 @@ module Amazon
706
731
  end
707
732
 
708
733
 
734
+ # Convert response groups to batch format, e.g. ItemSearch.1.ResponseGroup.
735
+ #
736
+ def batch_response_groups(operation)
737
+
738
+ rg = {}
739
+ op_count = Hash.new( 1 )
740
+
741
+ [ self, operation ].each do |op|
742
+ rg_hash = op.response_group.params
743
+
744
+ if m = rg_hash.to_a[0][0].match( /^(.+)\..+\..+$/ )
745
+ # This hash is already in batch format.
746
+ #
747
+ rg.merge!( rg_hash )
748
+
749
+ # Keep a record of the highest index currently in use for each type
750
+ # of operation.
751
+ #
752
+ rg_hash.each do |key, val|
753
+ op_kind, index = key.match( /^(.+)\.(\d+)\..+$/ )[1, 2]
754
+ if index.to_i == op_count[op_kind]
755
+ op_count[op_kind] += 1
756
+ end
757
+ end
758
+
759
+ else
760
+ # Convert hash to batch format.
761
+ #
762
+ rg_hash.each_value do |val|
763
+ rg_str = '%s.%d.ResponseGroup' % [ op.kind, op_count[op.kind] ]
764
+ op_count[op.kind] += 1
765
+ rg[rg_str] = val
766
+ end
767
+ end
768
+
769
+ end
770
+
771
+ rg
772
+ end
773
+
774
+
709
775
  # This class can be used to merge multiple operations into a single
710
776
  # operation for greater efficiency.
711
777
  #
712
778
  class MultipleOperation < Operation
713
779
 
714
- # This will allow you to take two Operation objects and combine them to
715
- # form a single object, which can then be used to perform a single
716
- # request to AWS. This allows for greater efficiency, reducing the
717
- # number of requests sent to AWS.
780
+ # This allows you to take two Operation objects and combine them to form
781
+ # a single object, which can then be used to perform a single request to
782
+ # AWS. This allows for greater efficiency, reducing the number of
783
+ # requests sent to AWS.
718
784
  #
719
- # AWS currently imposes a limit of two combined operations in a multiple
720
- # operation.
785
+ # AWS currently imposes a limit of two operations when encapsulating
786
+ # operations in a multiple operation.
721
787
  #
722
788
  # <em>operation1</em> and <em>operation2</em> are both objects from a
723
789
  # subclass of Operation, such as ItemSearch, ItemLookup, etc.
724
790
  #
725
- # There are currently a few restrictions in the Ruby/AWS implementation
726
- # of multiple operations:
791
+ # Please note the following implementation details:
727
792
  #
728
- # - ResponseGroup objects used when calling AWS::Search::Request#search
729
- # apply to both operations. You cannot use a different ResponseGroup
730
- # with each operation.
793
+ # - If you use the _response_group_ parameter of Search::Request#search
794
+ # to pass the list of response groups, it will apply to both
795
+ # operations.
796
+ #
797
+ # If you want to use a different response group set for each
798
+ # operation, you should assign the relevant groups to the
799
+ # @response_group attribute of each Operation object. You must do this
800
+ # *before* you instantiate the MultipleOperation.
731
801
  #
732
802
  # - One or both operations may have multiple results pages available,
733
803
  # but only the first page is returned. If you need the subsequent
734
804
  # pages, perform the operations separately, not as part of a
735
- # MultipleOperation.
805
+ # multiple operation.
736
806
  #
737
807
  # Example:
738
808
  #
739
809
  # is = ItemSearch.new( 'Books', { 'Title' => 'Ruby' } )
740
810
  # il = ItemLookup.new( 'ASIN', { 'ItemId' => 'B0013DZAYO',
741
811
  # 'MerchantId' => 'Amazon' } )
742
- # mo = MultipleOperation.new( is, il )
812
+ # is.response_group = ResponseGroup.new( :Large )
813
+ # il.response_group = ResponseGroup.new( :Small )
814
+ # mo = MultipleOperation.new( is, il )
743
815
  #
744
816
  # As you can see, the operations that are combined as a
745
817
  # MultipleOperation do not have to belong to the same class. In the
@@ -751,22 +823,14 @@ module Amazon
751
823
  #
752
824
  def initialize(operation1, operation2)
753
825
 
754
- # Safeguard against changing original Operation objects in place. This
755
- # is to protect me, not for user code.
756
- #
757
- operation1.freeze
758
- operation2.freeze
759
-
760
- op_kind = '%s,%s' % [ operation1.kind, operation2.kind ]
826
+ op_kind = [ operation1.kind, operation2.kind ].join( ',' )
761
827
 
762
828
  # Duplicate Operation objects and remove their Operation parameter.
763
829
  #
764
830
  op1 = operation1.dup
765
- op1.params = op1.params.dup
766
831
  op1.params.delete( 'Operation' )
767
832
 
768
833
  op2 = operation2.dup
769
- op2.params = op2.params.dup
770
834
  op2.params.delete( 'Operation' )
771
835
 
772
836
  if op1.class == op2.class
@@ -787,6 +851,9 @@ module Amazon
787
851
  params = { 'Operation' => op_kind }.merge( b_params )
788
852
  super( params )
789
853
 
854
+ @response_group = ResponseGroup.new( [] )
855
+ @response_group.params.delete( 'ResponseGroup' )
856
+ @response_group.params = op1.batch_response_groups( op2 )
790
857
  end
791
858
 
792
859
  end
@@ -861,6 +928,7 @@ module Amazon
861
928
  HealthPersonalCare
862
929
  Hobbies
863
930
  HomeGarden
931
+ HomeImprovement
864
932
  Industrial
865
933
  Jewelry
866
934
  KindleStore
@@ -877,12 +945,14 @@ module Amazon
877
945
  PCHardware
878
946
  PetSupplies
879
947
  Photo
880
- SilverMerchant
948
+ Shoes
949
+ SilverMerchants
881
950
  Software
882
951
  SoftwareVideoGames
883
952
  SportingGoods
884
953
  Tools
885
954
  Toys
955
+ UnboxVideo
886
956
  VHS
887
957
  Video
888
958
  VideoGames
@@ -1098,7 +1168,7 @@ module Amazon
1098
1168
  # <b>3P722DU4KUPCP</b> is retrieved from AWS.
1099
1169
  #
1100
1170
  def initialize(list_id, list_type, parameters={})
1101
- super( { 'ListId' => list_id,
1171
+ super( { 'ListId' => list_id,
1102
1172
  'ListType' => list_type
1103
1173
  }.merge( parameters ) )
1104
1174
  end
@@ -1304,27 +1374,45 @@ module Amazon
1304
1374
  end
1305
1375
 
1306
1376
  # Response groups determine which data pertaining to the item(s) being
1307
- # sought is returned. They can strongly influence the amount of data
1308
- # returned, so you should always use the smallest response group(s)
1309
- # containing the data of interest to you, to avoid masses of unnecessary
1310
- # data being returned.
1377
+ # sought is returned. They strongly influence the amount of data returned,
1378
+ # so you should always use the smallest response group(s) containing the
1379
+ # data of interest to you, to avoid masses of unnecessary data being
1380
+ # returned.
1311
1381
  #
1312
1382
  class ResponseGroup
1313
1383
 
1314
- attr_reader :list, :params
1384
+ # The default type of response group to use with each type of operation.
1385
+ #
1386
+ DEFAULT = { 'BrowseNodeLookup' => [ :BrowseNodeInfo, :TopSellers ],
1387
+ 'CustomerContentLookup' => [ :CustomerInfo, :CustomerLists ],
1388
+ 'CustomerContentSearch' => :CustomerInfo,
1389
+ 'Help' => :Help,
1390
+ 'ItemLookup' => :Large,
1391
+ 'ItemSearch' => :Large,
1392
+ 'ListLookup' => [ :ListInfo, :Small ],
1393
+ 'ListSearch' => :ListInfo,
1394
+ 'SellerListingLookup' => :SellerListing,
1395
+ 'SellerListingSearch' => :SellerListing,
1396
+ 'SellerLookup' => :Seller,
1397
+ 'SimilarityLookup' => :Large,
1398
+ 'TagLookup' => [ :Tags, :TagsSummary ],
1399
+ 'TransactionLookup' => :TransactionDetails,
1400
+ 'VehiclePartLookup' => :VehiclePartFit,
1401
+ 'VehiclePartSearch' => :VehicleParts,
1402
+ 'VehicleSearch' => :VehicleMakes
1403
+ }
1404
+
1405
+ attr_reader :list
1406
+ attr_accessor :params
1315
1407
 
1316
1408
  # Define a set of one or more response groups to be applied to items
1317
1409
  # retrieved by an AWS operation.
1318
1410
  #
1319
- # If no response groups are given in _rg_ when instantiating an object,
1320
- # *Small* will be used by default.
1321
- #
1322
1411
  # Example:
1323
1412
  #
1324
1413
  # rg = ResponseGroup.new( 'Medium', 'Offers', 'Reviews' )
1325
1414
  #
1326
1415
  def initialize(*rg)
1327
- rg << 'Small' if rg.empty?
1328
1416
  @list = rg
1329
1417
  @params = { 'ResponseGroup' => @list.join( ',' ) }
1330
1418
  end
@@ -1342,13 +1430,19 @@ module Amazon
1342
1430
  class AWSError < AmazonError; end
1343
1431
 
1344
1432
  def Error.exception(xml)
1345
- err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
1346
- err_msg = xml.elements['Message'].text
1433
+ err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
1434
+ err_msg = xml.elements['Message'].text
1347
1435
 
1348
1436
  # Dynamically define a new exception class for this class of error,
1349
1437
  # unless it already exists.
1350
1438
  #
1351
- unless Amazon::AWS::Error.const_defined?( err_class )
1439
+ # Note that Ruby 1.9's Module.const_defined? needs a second parameter
1440
+ # of *false*, or it will also search AWSError's ancestors.
1441
+ #
1442
+ cd_params = [ err_class ]
1443
+ cd_params << false if RUBY_VERSION >= '1.9.0'
1444
+
1445
+ unless Amazon::AWS::Error.const_defined?( *cd_params )
1352
1446
  Amazon::AWS::Error.const_set( err_class, Class.new( AWSError ) )
1353
1447
  end
1354
1448
 
@@ -1359,6 +1453,59 @@ module Amazon
1359
1453
 
1360
1454
  end
1361
1455
 
1456
+
1457
+ # Create a shorthand module method for each of the AWS operations. These
1458
+ # can be used to create less verbose code at the expense of flexibility.
1459
+ #
1460
+ # For example, we might normally write the following code:
1461
+ #
1462
+ # is = ItemSearch.new( 'Books', { 'Title' => 'Ruby' } )
1463
+ # rg = ResponseGroup.new( 'Large' )
1464
+ # req = Request.new
1465
+ # response = req.search( is, rg )
1466
+ #
1467
+ # but we could instead use ItemSearch's associated module method as
1468
+ # follows:
1469
+ #
1470
+ # response = Amazon::AWS.item_search( 'Books', { 'Title' => 'Ruby' } )
1471
+ #
1472
+ # Note that these equivalent module methods all attempt to use the *Large*
1473
+ # response group, which may or may not work. If an
1474
+ # Amazon::AWS::Error::InvalidResponseGroup is raised, we will scan the
1475
+ # text of the error message returned by AWS to try to glean a valid
1476
+ # response group and then retry the operation using that instead.
1477
+
1478
+
1479
+ # Ontain a list of all subclasses of the Operation class.
1480
+ #
1481
+ classes =
1482
+ ObjectSpace.enum_for( :each_object, class << Operation; self; end ).to_a
1483
+
1484
+ classes.each do |cl|
1485
+ # Convert class name to Ruby case, e.g. ItemSearch => item_search.
1486
+ #
1487
+ class_name = cl.to_s.sub( /^.+::/, '' )
1488
+ uncamelised_name = Amazon.uncamelise( class_name )
1489
+
1490
+ # Define the module method counterpart of each operation.
1491
+ #
1492
+ module_eval %Q(
1493
+ def AWS.#{uncamelised_name}(*params)
1494
+ # Instantiate an object of the desired operational class.
1495
+ #
1496
+ op = #{cl.to_s}.new( *params )
1497
+
1498
+ # Attempt a search for the given operation using its default
1499
+ # response group types.
1500
+ #
1501
+ results = Search::Request.new.search( op )
1502
+ yield results if block_given?
1503
+ return results
1504
+
1505
+ end
1506
+ )
1507
+ end
1508
+
1362
1509
  end
1363
1510
 
1364
1511
  end