ruby-aaws 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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