Class: BridgeAPI::Client

Inherits:
Footrest::Client
  • Object
show all
Defined in:
lib/bridge_api/client.rb,
lib/bridge_api/client/role.rb,
lib/bridge_api/client/user.rb,
lib/bridge_api/client/group.rb,
lib/bridge_api/client/account.rb,
lib/bridge_api/client/manager.rb,
lib/bridge_api/client/program.rb,
lib/bridge_api/client/data_dump.rb,
lib/bridge_api/client/enrollment.rb,
lib/bridge_api/client/affiliation.rb,
lib/bridge_api/client/live_course.rb,
lib/bridge_api/client/sub_account.rb,
lib/bridge_api/client/clone_object.rb,
lib/bridge_api/client/custom_field.rb,
lib/bridge_api/client/learner_item.rb,
lib/bridge_api/client/course_template.rb,
lib/bridge_api/client/program_enrollment.rb,
lib/bridge_api/client/live_course_session.rb,
lib/bridge_api/client/live_course_enrollment.rb

Defined Under Namespace

Modules: Account, Affiliation, CloneObject, CourseTemplate, CustomField, DataDump, Enrollment, Group, LearnerItem, LiveCourse, LiveCourseEnrollment, LiveCourseSession, Manager, Program, ProgramEnrollment, Role, SubAccount, User

Constant Summary collapse

DATA_DUMP_DOWNLOAD_PATH =
'/data_dumps/download'
DATA_DUMP_PATH =
'/data_dumps'
COURSE_TEMPLATE_PATH =
'/course_templates'
ENROLLMENT_PATH =
'/enrollments'
LTI_TOOLS_PATH =
'/lti_tools'
PROGRAM_PATH =
'/programs'
PROGRAM_ENROLLMENT_PATH =
'/learners'
USER_PATH =
'/users'
GROUPS_PATH =
'/groups'
MANAGER_PATH =
'/managers'
ADMIN_PATH =
'/admin'
AUTHOR_PATH =
'/author'
LEARNER_PATH =
'/learner'
LEARNERS_PATH =
'/learners'
LEARNER_ITEMS_PATH =
'/learner_items'
CUSTOM_FIELD_PATH =
'/custom_fields'
SUB_ACCOUNT_PATH =
'/sub_accounts'
SUPPORT_PATH =
'/support'
ACCOUNT_PATH =
'/accounts'
CLONE_OBJECTS_PATH =
'/clone_objects'
API_VERSION =
1
API_PATH =
'/api'
ROLE_PATH =
'/roles'
AFFILIATED_SUBACCOUNTS =
'/affiliated_sub_accounts'
BATCH_PATH =
'/batch'
LIVE_COURSES_PATH =
'/live_courses'
SESSIONS_PATH =
'/sessions'
PUBLISH_PATH =
'/publish'
WEB_CONFERENCE_PATH =
'/web_conference'
RESTORE_PATH =
'/restore'
DUE_DATE_PATH =
'/due_date'
RESET_PATH =
'/reset'
RESULT_MAPPING =
{}

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, &block) ⇒ Client

Returns a new instance of Client.



56
57
58
59
60
61
# File 'lib/bridge_api/client.rb', line 56

def initialize(options = {}, &block)
  if BridgeAPI.enforce_rate_limits && has_token_pool?(options)
    options = initialize_from_token_pool(options)
  end
  super
end

Instance Method Details

#apply_rate_limits(response) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/bridge_api/client.rb', line 141

def apply_rate_limits(response)
  limit = response.headers['x-rate-limit-remaining']
  return if limit.nil?

  BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit} for key #{config[:api_key]}")
  self.limit_remaining = limit.to_i
end

#convert_tokens(config) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bridge_api/client.rb', line 76

def convert_tokens(config)
  return config unless config.has_key?(:api_tokens)
  return config unless config[:api_tokens].is_a?(Array)
  config[:api_keys] ||= {}
  config[:api_tokens].each do |token|
    decoded_token_array = Base64.strict_decode64(token).split(':')
    config[:api_keys][decoded_token_array[0]] = decoded_token_array[1]
  end
  config.delete(:api_tokens)
  config
end

#enforce_rate_limitsObject



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/bridge_api/client.rb', line 125

def enforce_rate_limits
  return unless rate_limit_reached?

  rotate_token! 
  if rate_limit_reached?
    sleep_time = rand(BridgeAPI.max_sleep_seconds)
    sleep_time = BridgeAPI.min_sleep_seconds if sleep_time < BridgeAPI.min_sleep_seconds 
    BridgeAPI.logger.debug("Rate limit reached sleeping for #{sleep_time}")
    sleep(sleep_time)
  end
end

#get_next_key(keys, current_key) ⇒ Object



109
110
111
112
113
114
115
116
117
# File 'lib/bridge_api/client.rb', line 109

def get_next_key(keys, current_key)
  keys.delete(current_key)
  usable_key = keys.find do |key|
    limit = rate_limit(key)
    current_key_limit = limit.present? ? limit.fetch('current') : 0
    BridgeAPI.beginning_rate_limit - current_key_limit > BridgeAPI.rate_limit_threshold
  end
  usable_key || keys[rand(keys.length)]
end

#has_token_pool?(config) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
74
# File 'lib/bridge_api/client.rb', line 71

def has_token_pool?(config)
  config[:api_keys].is_a?(Hash) && config[:api_keys].keys.count >= 1 ||
    config[:api_tokens].is_a?(Array) && config[:api_tokens].count >= 1
end

#initialize_from_token_pool(config) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/bridge_api/client.rb', line 88

def initialize_from_token_pool(config)
  config = convert_tokens(config)  
  creds = config[:api_keys].first
  config[:api_key]    ||= creds[0]
  config[:api_secret] ||= creds[1]
  config
end

#limit_remainingObject



149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/bridge_api/client.rb', line 149

def limit_remaining
  if using_master_rate_limit?
    limit = rate_limit(config[:api_key])
    if limit.nil?
      set_rate_limit(config[:api_key], 0)
      limit = { current: 0 }.with_indifferent_access
    end
    limit['current']
  else
    BridgeAPI.rate_limits[config[:api_key]]
  end
end

#limit_remaining=(value) ⇒ Object



162
163
164
165
166
167
168
169
# File 'lib/bridge_api/client.rb', line 162

def limit_remaining=(value)
  if using_master_rate_limit?
    set_rate_limit(config[:api_key], value)
  else
    BridgeAPI.rate_limits[config[:api_key]] = value
  end
  refresh_stale_tokens
end

#rate_limit(key) ⇒ Object



177
178
179
180
181
# File 'lib/bridge_api/client.rb', line 177

def rate_limit(key)
  BridgeAPI.master_mutex.synchronize do
    PaulWalker::RateLimit.get(key, key)
  end
end

#rate_limit_reached?Boolean

Returns:

  • (Boolean)


119
120
121
122
123
# File 'lib/bridge_api/client.rb', line 119

def rate_limit_reached?
  return false unless BridgeAPI.enforce_rate_limits && limit_remaining.present?

  limit_remaining < BridgeAPI.rate_limit_threshold
end

#refresh_stale_tokensObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/bridge_api/client.rb', line 183

def refresh_stale_tokens
  return unless using_master_rate_limit?
  return unless config[:api_keys].present?
  api_keys = config[:api_keys].keys
  api_keys.delete(config[:api_key])
  api_keys.each do |key|
    limit = rate_limit(key)
    next unless limit.present?
    if limit['timestamp'].present? && DateTime.parse(limit['timestamp']) < 1.minute.ago
      BridgeAPI.logger.debug("Refreshing: #{key}")
      set_rate_limit(key, 0)
    end
  end
end

#request(method, &block) ⇒ Object

Override Footrest request for ApiArray support



64
65
66
67
68
69
# File 'lib/bridge_api/client.rb', line 64

def request(method, &block)
  enforce_rate_limits if rate_limit_reached?
  response = connection.send(method, &block)
  apply_rate_limits(response)
  ApiArray.process_response(response, self, RESULT_MAPPING)
end

#rotate_token!Object

rotates to the next token in the pool (by order in which they were provided)



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/bridge_api/client.rb', line 97

def rotate_token! 
  return unless config[:api_keys].present?
  old_api_key = config[:api_key]
  keys = config[:api_keys].keys
  return if keys.count <= 1
  key                 = get_next_key(keys, config[:api_key])
  config[:api_key]    = key
  config[:api_secret] = config[:api_keys][key]
  set_connection(config)
  BridgeAPI.logger.debug("ROTATED TO KEY: #{config[:api_key]}")
end

#set_connection(config) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/bridge_api/client.rb', line 198

def set_connection(config)
  config[:logger] = config[:logging] if config[:logging]
  @connection     = Faraday.new(url: config[:prefix]) do |faraday|
    faraday.request :multipart
    faraday.request :url_encoded
    if config[:logger] == true
      faraday.response :logger
    elsif config[:logger]
      faraday.use Faraday::Response::Logger, config[:logger]
    end
    faraday.use Footrest::FollowRedirects, limit: 5 unless config[:follow_redirects] == false
    faraday.adapter Faraday.default_adapter
    faraday.use Footrest::ParseJson, content_type: /\bjson$/
    faraday.use Footrest::RaiseFootrestErrors
    faraday.use Footrest::Pagination
    faraday.headers[:accept]     = 'application/json'
    faraday.headers[:user_agent] = 'Footrest'
    if config[:api_key] && config[:api_secret]
      faraday.headers[:authorization] = 'Basic ' + Base64.strict_encode64("#{config[:api_key]}:#{config[:api_secret]}")
    elsif config[:token]
      faraday.headers[:authorization] = "Bearer #{config[:token]}"
    else
      raise 'No api authorization provided'
    end
  end
end

#set_rate_limit(key, limit) ⇒ Object



171
172
173
174
175
# File 'lib/bridge_api/client.rb', line 171

def set_rate_limit(key, limit)
  BridgeAPI.master_mutex.synchronize do
    PaulWalker::RateLimit.add(key, key, limit, BridgeAPI.beginning_rate_limit)
  end
end

#using_master_rate_limit?Boolean

Returns:

  • (Boolean)


137
138
139
# File 'lib/bridge_api/client.rb', line 137

def using_master_rate_limit?
  config[:master_rate_limit].present? || BridgeAPI.master_rate_limit.present?
end