appnexusapi 1.0.0 → 1.1.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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +71 -39
  4. data/appnexusapi.gemspec +1 -2
  5. data/lib/appnexusapi.rb +21 -10
  6. data/lib/appnexusapi/configuration.rb +12 -0
  7. data/lib/appnexusapi/connection.rb +83 -90
  8. data/lib/appnexusapi/error.rb +3 -0
  9. data/lib/appnexusapi/faraday/raise_http_error.rb +15 -1
  10. data/lib/appnexusapi/read_only_service.rb +8 -0
  11. data/lib/appnexusapi/resource.rb +19 -15
  12. data/lib/appnexusapi/service.rb +35 -48
  13. data/lib/appnexusapi/services/ad_server_service.rb +9 -0
  14. data/lib/appnexusapi/{advertiser_service.rb → services/advertiser_service.rb} +0 -0
  15. data/lib/appnexusapi/{bidder_instance_service.rb → services/bidder_instance_service.rb} +0 -6
  16. data/lib/appnexusapi/{bidder_profile_service.rb → services/bidder_profile_service.rb} +0 -6
  17. data/lib/appnexusapi/{bidder_service.rb → services/bidder_service.rb} +0 -0
  18. data/lib/appnexusapi/services/brand_service.rb +2 -0
  19. data/lib/appnexusapi/services/browser_service.rb +2 -0
  20. data/lib/appnexusapi/{campaign_service.rb → services/campaign_service.rb} +0 -0
  21. data/lib/appnexusapi/services/category_service.rb +5 -0
  22. data/lib/appnexusapi/{content_category_service.rb → services/content_category_service.rb} +0 -1
  23. data/lib/appnexusapi/{creative_service.rb → services/creative_service.rb} +0 -0
  24. data/lib/appnexusapi/{creative_template_service.rb → services/creative_template_service.rb} +1 -6
  25. data/lib/appnexusapi/services/device_model_service.rb +2 -0
  26. data/lib/appnexusapi/{domain_list_service.rb → services/domain_list_service.rb} +0 -0
  27. data/lib/appnexusapi/services/inventory_attribute_service.rb +2 -0
  28. data/lib/appnexusapi/{inventory_source_service.rb → services/inventory_source_service.rb} +0 -0
  29. data/lib/appnexusapi/services/language_service.rb +2 -0
  30. data/lib/appnexusapi/{line_item_service.rb → services/line_item_service.rb} +0 -0
  31. data/lib/appnexusapi/services/log_level_data_download_service.rb +13 -0
  32. data/lib/appnexusapi/services/log_level_data_service.rb +105 -0
  33. data/lib/appnexusapi/services/media_type_service.rb +2 -0
  34. data/lib/appnexusapi/{member_service.rb → services/member_service.rb} +0 -0
  35. data/lib/appnexusapi/{object_limit_service.rb → services/object_limit_service.rb} +0 -0
  36. data/lib/appnexusapi/{operating_system_extended_service.rb → services/operating_system_extended_service.rb} +1 -6
  37. data/lib/appnexusapi/services/operating_system_service.rb +2 -0
  38. data/lib/appnexusapi/{payment_rule_service.rb → services/payment_rule_service.rb} +0 -0
  39. data/lib/appnexusapi/{placement_service.rb → services/placement_service.rb} +0 -0
  40. data/lib/appnexusapi/services/platform_member_service.rb +2 -0
  41. data/lib/appnexusapi/{profile_service.rb → services/profile_service.rb} +0 -0
  42. data/lib/appnexusapi/{publisher_service.rb → services/publisher_service.rb} +0 -0
  43. data/lib/appnexusapi/{segment_service.rb → services/segment_service.rb} +0 -2
  44. data/lib/appnexusapi/{site_service.rb → services/site_service.rb} +0 -0
  45. data/lib/appnexusapi/services/technical_attribute_service.rb +2 -0
  46. data/lib/appnexusapi/{tiny_tag_service.rb → services/tiny_tag_service.rb} +0 -6
  47. data/lib/appnexusapi/{user_service.rb → services/user_service.rb} +0 -0
  48. data/lib/appnexusapi/version.rb +1 -1
  49. data/spec/connection_spec.rb +46 -10
  50. data/spec/fixtures/vcr/content_category_crud.yml +188 -69
  51. data/spec/fixtures/vcr/log_level_data_service_download.yml +213 -0
  52. data/spec/integration/content_category_spec.rb +4 -0
  53. data/spec/{creative_service_spec.rb → integration/creative_service_spec.rb} +0 -0
  54. data/spec/integration/log_level_data_service_spec.rb +13 -0
  55. data/spec/{object_limit_service_spec.rb → integration/object_limit_service_spec.rb} +0 -0
  56. data/spec/integration/placement_spec.rb +0 -1
  57. data/spec/spec_helper.rb +2 -3
  58. metadata +52 -94
  59. data/lib/appnexusapi/ad_server_resource.rb +0 -2
  60. data/lib/appnexusapi/ad_server_service.rb +0 -20
  61. data/lib/appnexusapi/advertiser_resource.rb +0 -2
  62. data/lib/appnexusapi/bidder_instance_resource.rb +0 -2
  63. data/lib/appnexusapi/bidder_profile_resource.rb +0 -2
  64. data/lib/appnexusapi/bidder_resource.rb +0 -2
  65. data/lib/appnexusapi/brand_resource.rb +0 -2
  66. data/lib/appnexusapi/brand_service.rb +0 -8
  67. data/lib/appnexusapi/browser_resource.rb +0 -2
  68. data/lib/appnexusapi/browser_service.rb +0 -8
  69. data/lib/appnexusapi/campaign_resource.rb +0 -2
  70. data/lib/appnexusapi/category_resource.rb +0 -2
  71. data/lib/appnexusapi/category_service.rb +0 -12
  72. data/lib/appnexusapi/content_category_resource.rb +0 -2
  73. data/lib/appnexusapi/creative_resource.rb +0 -2
  74. data/lib/appnexusapi/creative_template_resource.rb +0 -2
  75. data/lib/appnexusapi/device_model_resource.rb +0 -2
  76. data/lib/appnexusapi/device_model_service.rb +0 -6
  77. data/lib/appnexusapi/domain_list_resource.rb +0 -2
  78. data/lib/appnexusapi/inventory_attribute_resource.rb +0 -2
  79. data/lib/appnexusapi/inventory_attribute_service.rb +0 -8
  80. data/lib/appnexusapi/inventory_source_resource.rb +0 -2
  81. data/lib/appnexusapi/language_resource.rb +0 -2
  82. data/lib/appnexusapi/language_service.rb +0 -8
  83. data/lib/appnexusapi/line_item_resource.rb +0 -2
  84. data/lib/appnexusapi/log_level_data_download_service.rb +0 -68
  85. data/lib/appnexusapi/log_level_data_resource.rb +0 -19
  86. data/lib/appnexusapi/log_level_data_service.rb +0 -26
  87. data/lib/appnexusapi/media_type_resource.rb +0 -2
  88. data/lib/appnexusapi/media_type_service.rb +0 -8
  89. data/lib/appnexusapi/member_resource.rb +0 -2
  90. data/lib/appnexusapi/object_limit_resource.rb +0 -2
  91. data/lib/appnexusapi/operating_system_extended_resource.rb +0 -2
  92. data/lib/appnexusapi/operating_system_resource.rb +0 -2
  93. data/lib/appnexusapi/operating_system_service.rb +0 -8
  94. data/lib/appnexusapi/payment_rule_resource.rb +0 -2
  95. data/lib/appnexusapi/placement_resource.rb +0 -2
  96. data/lib/appnexusapi/platform_member_resource.rb +0 -2
  97. data/lib/appnexusapi/platform_member_service.rb +0 -8
  98. data/lib/appnexusapi/profile_resource.rb +0 -2
  99. data/lib/appnexusapi/publisher_resource.rb +0 -2
  100. data/lib/appnexusapi/segment_resource.rb +0 -2
  101. data/lib/appnexusapi/site_resource.rb +0 -2
  102. data/lib/appnexusapi/technical_attribute_resource.rb +0 -2
  103. data/lib/appnexusapi/technical_attribute_service.rb +0 -8
  104. data/lib/appnexusapi/tiny_tag_resource.rb +0 -2
  105. data/lib/appnexusapi/user_resource.rb +0 -2
@@ -0,0 +1,8 @@
1
+ module AppnexusApi
2
+ class ReadOnlyService < Service
3
+ def initialize(connection)
4
+ @read_only = true
5
+ super(connection)
6
+ end
7
+ end
8
+ end
@@ -1,39 +1,43 @@
1
1
  class AppnexusApi::Resource
2
2
 
3
- attr_reader :dbg_info
3
+ attr_reader :dbg_info, :raw_json
4
4
 
5
5
  def initialize(json, service, dbg_info = nil)
6
- @json = json
6
+ @raw_json = json
7
7
  @service = service
8
8
  @dbg_info = dbg_info
9
9
  end
10
10
 
11
- def update(route_params={}, body_params={})
12
- resource = @service.update(id, route_params, body_params)
13
- @json = resource.raw_json
11
+ def update(route_params = {}, body_params = {})
12
+ @raw_json = @service.update(id, route_params, body_params).raw_json
14
13
  self
15
14
  end
16
15
 
17
- def delete(route_params={})
18
- @service.delete(id, route_params)
16
+ # If you have modified the @raw_json hash in place, you can just do resource.save
17
+ def save
18
+ @service.update(id, {}, @raw_json).raw_json
19
+ self
19
20
  end
20
21
 
21
- def raw_json
22
- @json
22
+ def delete(route_params = {})
23
+ @service.delete(id, route_params)
23
24
  end
24
25
 
25
26
  def method_missing(sym, *args, &block)
26
- if @json.respond_to?(sym)
27
- @json.send(sym, *args, &block)
28
- elsif @json.has_key?(sym.to_s)
29
- return @json[sym.to_s]
27
+ if @raw_json.respond_to?(sym)
28
+ @raw_json.send(sym, *args, &block)
29
+ elsif @raw_json.key?(sym.to_s)
30
+ @raw_json[sym.to_s]
30
31
  else
31
32
  super(sym, *args, &block)
32
33
  end
33
34
  end
34
35
 
35
- def to_s
36
- @json.inspect
36
+ def respond_to_missing?(method_name, include_private = false)
37
+ @raw_json.respond_to?(method_name) || @raw_json.key?(method_name.to_s) || super
37
38
  end
38
39
 
40
+ def to_s
41
+ @raw_json.inspect
42
+ end
39
43
  end
@@ -7,7 +7,7 @@ class AppnexusApi::Service
7
7
 
8
8
  def name
9
9
  @name ||= begin
10
- str = self.class.name.split("::").last.gsub("Service", "")
10
+ str = self.class.name.split('::').last.sub(/Service\z/, '')
11
11
  str.gsub(/(.)([A-Z])/, '\1_\2').downcase
12
12
  end
13
13
  end
@@ -16,13 +16,6 @@ class AppnexusApi::Service
16
16
  name + 's'
17
17
  end
18
18
 
19
- def resource_class
20
- @resource_class ||= begin
21
- resource_name = name.capitalize.gsub(/(_(.))/) { |c| $2.upcase }
22
- AppnexusApi.const_get(resource_name + "Resource")
23
- end
24
- end
25
-
26
19
  def uri_name
27
20
  name.gsub('_', '-')
28
21
  end
@@ -35,20 +28,12 @@ class AppnexusApi::Service
35
28
  uri_name
36
29
  end
37
30
 
38
- def get(params={})
39
- return_response = params.delete(:return_response) || false
40
- params = {
41
- "num_elements" => DEFAULT_NUMBER_OF_ELEMENTS,
42
- "start_element" => 0
43
- }.merge(params)
44
- response = @connection.get(uri_suffix, params).body['response']
45
- if return_response
46
- response
47
- else
48
- parse_response(response)
49
- end
31
+ def get(params = {})
32
+ params = { 'num_elements' => DEFAULT_NUMBER_OF_ELEMENTS, 'start_element' => 0 }.merge(params)
33
+ parse_response(@connection.get(uri_suffix, params).body['response'])
50
34
  end
51
35
 
36
+ # Page through all available elements automatically
52
37
  def get_all(params = {})
53
38
  responses = []
54
39
  last_responses = get(params)
@@ -61,54 +46,56 @@ class AppnexusApi::Service
61
46
  responses
62
47
  end
63
48
 
64
- def create(route_params={}, body={})
65
- raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
66
- body = { uri_name => body }
49
+ def create(route_params = {}, body = {})
50
+ check_read_only!
67
51
  route = @connection.build_url(uri_suffix, route_params)
68
- response = @connection.post(route, body).body['response']
69
- if response['error_id']
70
- response.delete('dbg')
71
- raise AppnexusApi::BadRequest.new(response.inspect)
72
- end
52
+ response = @connection.post(route, { uri_name => body }).body['response']
53
+ validate_response!(response)
54
+
73
55
  parse_response(response).first
74
56
  end
75
57
 
76
- def update(id, route_params={}, body={})
77
- raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
78
- body = { uri_name => body }
79
- route = @connection.build_url(uri_suffix, route_params.merge("id" => id))
80
- response = @connection.put(route, body).body['response']
81
- if response['error_id']
82
- response.delete('dbg')
83
- raise AppnexusApi::BadRequest.new(response.inspect)
84
- end
58
+ def update(id, route_params = {}, body = {})
59
+ check_read_only!
60
+ route = @connection.build_url(uri_suffix, route_params.merge('id' => id))
61
+ response = @connection.put(route, { uri_name => body }).body['response']
62
+ validate_response!(response)
63
+
85
64
  parse_response(response).first
86
65
  end
87
66
 
88
67
  def delete(id, route_params)
89
- raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
90
- route = @connection.build_url(uri_suffix, route_params.merge("id" => id))
68
+ check_read_only!
69
+ route = @connection.build_url(uri_suffix, route_params.merge('id' => id))
91
70
  response = @connection.delete(route).body['response']
92
- if response['error_id']
93
- response.delete('dbg')
94
- raise AppnexusApi::BadRequest.new(response.inspect)
95
- end
71
+ validate_response!(response)
72
+
96
73
  response
97
74
  end
98
75
 
76
+ private
77
+
78
+ def check_read_only!
79
+ raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
80
+ end
81
+
82
+ def validate_response!(response)
83
+ return unless response['error_id']
84
+
85
+ response.delete('dbg')
86
+ raise AppnexusApi::BadRequest.new(response.inspect)
87
+ end
88
+
99
89
  def parse_response(response)
100
90
  case key = resource_name(response)
101
91
  when plural_name, plural_uri_name
102
- response[key].map do |json|
103
- resource_class.new(json, self, response['dbg'])
104
- end
92
+ response[key].map { |json| AppnexusApi::Resource.new(json, self, response['dbg']) }
105
93
  when name, uri_name
106
- [resource_class.new(response[key], self, response['dbg'])]
94
+ [AppnexusApi::Resource.new(response[key], self, response['dbg'])]
107
95
  end
108
96
  end
109
97
 
110
98
  def resource_name(response)
111
99
  [plural_name, plural_uri_name, name, uri_name].detect { |n| response.key?(n) }
112
100
  end
113
-
114
101
  end
@@ -0,0 +1,9 @@
1
+ class AppnexusApi::AdServerService < AppnexusApi::ReadOnlyService
2
+ def name
3
+ "adserver"
4
+ end
5
+
6
+ def uri_suffix
7
+ "ad-server"
8
+ end
9
+ end
@@ -1,5 +1,4 @@
1
1
  class AppnexusApi::BidderInstanceService < AppnexusApi::Service
2
-
3
2
  def initialize(connection, bidder_id)
4
3
  @bidder_id = bidder_id
5
4
  super(connection)
@@ -9,10 +8,6 @@ class AppnexusApi::BidderInstanceService < AppnexusApi::Service
9
8
  "instance"
10
9
  end
11
10
 
12
- def resource_class
13
- AppnexusApi::BidderInstanceResource
14
- end
15
-
16
11
  def uri_suffix
17
12
  "bidder-instance/#{@bidder_id}"
18
13
  end
@@ -20,5 +15,4 @@ class AppnexusApi::BidderInstanceService < AppnexusApi::Service
20
15
  def delete(id)
21
16
  raise AppnexusApi::NotImplemented, "To remove an instance, please set it to inactive."
22
17
  end
23
-
24
18
  end
@@ -1,5 +1,4 @@
1
1
  class AppnexusApi::BidderProfileService < AppnexusApi::Service
2
-
3
2
  def initialize(connection, bidder_id)
4
3
  @bidder_id = bidder_id
5
4
  super(connection)
@@ -9,10 +8,6 @@ class AppnexusApi::BidderProfileService < AppnexusApi::Service
9
8
  "profile"
10
9
  end
11
10
 
12
- def resource_class
13
- AppnexusApi::BidderProfileResource
14
- end
15
-
16
11
  def uri_suffix
17
12
  "profile/#{@bidder_id}"
18
13
  end
@@ -20,5 +15,4 @@ class AppnexusApi::BidderProfileService < AppnexusApi::Service
20
15
  def delete(id)
21
16
  raise AppnexusApi::NotImplemented
22
17
  end
23
-
24
18
  end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::BrandService < AppnexusApi::ReadOnlyService
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::BrowserService < AppnexusApi::ReadOnlyService
2
+ end
@@ -0,0 +1,5 @@
1
+ class AppnexusApi::CategoryService < AppnexusApi::ReadOnlyService
2
+ def plural_name
3
+ "categories"
4
+ end
5
+ end
@@ -1,5 +1,4 @@
1
1
  class AppnexusApi::ContentCategoryService < AppnexusApi::Service
2
-
3
2
  def initialize(connection)
4
3
  super(connection)
5
4
  end
@@ -3,16 +3,11 @@ class AppnexusApi::CreativeTemplateService < AppnexusApi::Service
3
3
  "template"
4
4
  end
5
5
 
6
- def resource_class
7
- AppnexusApi::CreativeTemplateResource
8
- end
9
-
10
6
  def uri_suffix
11
- "template"
7
+ name
12
8
  end
13
9
 
14
10
  def delete(id)
15
11
  raise AppnexusApi::NotImplemented
16
12
  end
17
-
18
13
  end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::DeviceModelService < AppnexusApi::ReadOnlyService
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::InventoryAttributeService < AppnexusApi::ReadOnlyService
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::LanguageService < AppnexusApi::ReadOnlyService
2
+ end
@@ -0,0 +1,13 @@
1
+ require 'appnexusapi/services/log_level_data_service'
2
+
3
+ # TODO: Remove this class in v2.0
4
+ module AppnexusApi
5
+ class LogLevelDataDownloadService < LogLevelDataService
6
+ extend Gem::Deprecate
7
+
8
+ def initialize(connection, options = {})
9
+ super
10
+ end
11
+ deprecate :initialize, 'LogLevelDataService#initialize', 2017, 6
12
+ end
13
+ end
@@ -0,0 +1,105 @@
1
+ module AppnexusApi
2
+ class LogLevelDataService < AppnexusApi::ReadOnlyService
3
+ DEFAULT_FEED = 'standard_feed'.freeze
4
+ DOWNLOAD_URI = 'siphon-download'.freeze
5
+ RETRY_DOWNLOAD_PARAMS = {
6
+ base_interval: 30,
7
+ tries: 20,
8
+ max_elapsed_time: 3600,
9
+ on: [AppnexusApi::Error, ::Faraday::Error::ClientError],
10
+ on_retry: Proc.new do |exception, tries|
11
+ AppnexusApi.config.logger.warn("Retrying after #{exception.class}: #{tries} attempts.")
12
+ end
13
+ }.freeze
14
+
15
+ def initialize(connection, options = {})
16
+ @downloaded_files_path = options[:downloaded_files_path] || '.'
17
+ @siphon_name = options[:siphon_name] || DEFAULT_FEED
18
+ super(connection)
19
+ end
20
+
21
+ def download_new_files_since(time = nil)
22
+ since(time).map { |siphon| download_resource(siphon) }
23
+ end
24
+
25
+ def uri_name
26
+ 'siphon'
27
+ end
28
+
29
+ def since(time = nil)
30
+ params = {}
31
+ params[:siphon_name] = @siphon_name if @siphon_name
32
+ params[:updated_since] = time.strftime('%Y_%m_%d_%H') if time
33
+ siphons = get(params) || {}
34
+
35
+ # Anything with the same name and hour but with a newer timestamp is a republished replacement for an older file.
36
+ # When this happens appnexus is supposed to set the checksum for the old file to NULL but they do not always
37
+ # actually do this, so we have to manually reject invalid entries.
38
+ siphons.reject do |siphon|
39
+ (siphons - [siphon]).any? { |s| s.name == siphon.name && s.hour == siphon.hour && s.timestamp > siphon.timestamp }
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Parameter is a LogLevelDataResource FKA a "Siphon"
46
+ # Downloads a gzipped file
47
+ # Returns an array of paths to downloaded files
48
+ def download_resource(siphon)
49
+ fail 'Missing necessary information!' unless siphon.name && siphon.hour && siphon.timestamp && siphon.splits
50
+
51
+ download_params = siphon.splits.map do |split_part|
52
+ # In the case of files that were later replaced, there should be no checksum and they shouldn't be downloaded
53
+ next nil if split_part['checksum'].nil? || split_part['checksum'].empty?
54
+
55
+ {
56
+ split_part: split_part['part'],
57
+ siphon_name: siphon.name,
58
+ timestamp: siphon.timestamp,
59
+ hour: siphon.hour,
60
+ checksum: split_part['checksum']
61
+ }
62
+ end.compact
63
+
64
+ download_params.map do |params|
65
+ uri = URI.parse(@connection.get(DOWNLOAD_URI, params.reject { |k, v| k == :checksum }).headers['location'])
66
+ filename = File.join(@downloaded_files_path, "#{params[:siphon_name]}_#{params[:hour]}_#{params[:split_part]}.gz")
67
+
68
+ Retriable.retriable(RETRY_DOWNLOAD_PARAMS) do
69
+ download_file(uri, filename)
70
+ calculated_checksum = Digest::MD5.hexdigest(File.read(filename))
71
+ if calculated_checksum != params[:checksum]
72
+ error_message = "Calculated checksum of #{calculated_checksum} doesn't match API provided checksum #{params[:checksum]}"
73
+ AppnexusApi.config.logger.fatal(error_message)
74
+ raise BadChecksumException, error_message
75
+ end
76
+ end
77
+
78
+ filename
79
+ end
80
+ end
81
+
82
+ def download_file(uri, filename)
83
+ AppnexusApi.config.logger.debug("Starting HTTP download for: #{uri.to_s}...")
84
+ http_object = Net::HTTP.new(uri.host, uri.port)
85
+ http_object.use_ssl = true if uri.scheme == 'https'
86
+
87
+ begin
88
+ http_object.start do |http|
89
+ request = Net::HTTP::Get.new(uri.request_uri)
90
+ http.read_timeout = 500
91
+
92
+ http.request(request) do |response|
93
+ open(filename, 'wb') do |io|
94
+ response.read_body do |chunk|
95
+ io.write(chunk)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ AppnexusApi.config.logger.info("Stored download of #{uri.to_s} as #{filename}")
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::MediaTypeService < AppnexusApi::ReadOnlyService
2
+ end