class Itrp::Client
Constants
- DEFAULT_HEADER
- MAX_PAGE_SIZE
- URI_ESCAPE_PATTERN
- VERSION
Public Class Methods
Create a new ITRP Client
Shared configuration for all ITRP Clients:
Itrp.configure do |config| config.api_token = 'd41f5868feb65fc87fa2311a473a8766ea38bc40' config.account = 'my-sandbox' ... end
Override configuration per ITRP Client: itrp = Itrp::Client.new
(account: 'trusted-sandbox')
All options available:
- logger: The Ruby Logger instance, default: Logger.new(STDOUT) - host: The ITRP API host, default: 'https://api.itrp.com' - api_version: The ITRP API version, default: 'v1' - api_token: *required* The ITRP API token - account: Specify a different (trusted) account to work with @see http://developer.itrp.com/v1/#multiple-accounts - source: The Source used when creating new records @see http://developer.itrp.com/v1/general/source/ - max_retry_time: maximum nr of seconds to wait for server to respond (default = 5400 = 1.5 hours) the sleep time between retries starts at 2 seconds and doubles after each retry retry times: 2, 6, 18, 54, 162, 486, 1458, 4374, 13122, ... seconds one retry will always be performed unless you set the value to -1 - read_timeout: HTTP GET read timeout in seconds (default = 25) - block_at_rate_limit: Set to +true+ to block the request until the rate limit is lifted, default: +false+ @see http://developer.itrp.com/v1/#rate-limiting - proxy_host: Define in case HTTP traffic needs to go through a proxy - proxy_port: Port of the proxy, defaults to 8080 - proxy_user: Proxy user - proxy_password: Proxy password
# File lib/itrp/client.rb, line 60 def initialize(options = {}) @options = Itrp.configuration.current.merge(options) [:host, :api_version, :api_token].each do |required_option| raise ::Itrp::Exception.new("Missing required configuration option #{required_option}") if option(required_option).blank? end @ssl, @domain, @port = ssl_domain_port_path(option(:host)) @ssl_verify_none = options[:ssl_verify_none] @logger = @options[:logger] end
Public Instance Methods
send HTTPS DELETE request and return instance of Itrp::Response
# File lib/itrp/client.rb, line 102 def delete(path, params = {}, header = {}) _send(Net::HTTP::Delete.new(expand_path(path, params), expand_header(header))) end
Yield all retrieved resources one-by-one for the given (paged) API query. Raises an ::Itrp::Exception
with the response retrieved from ITRP is invalid Returns total nr of resources yielded (for logging)
# File lib/itrp/client.rb, line 78 def each(path, params = {}, header = {}, &block) # retrieve the resources using the max page size (least nr of API calls) next_path = expand_path(path, {per_page: MAX_PAGE_SIZE, page: 1}.merge(params)) size = 0 while next_path # retrieve the records (with retry and optionally wait for rate-limit) response = get(next_path, {}, header) # raise exception in case the response is invalid raise ::Itrp::Exception.new(response.message) unless response.valid? # yield the resources response.json.each{ |resource| yield resource } size += response.json.size # go to the next page next_path = response.pagination_relative_link(:next) end size end
Export CSV files @param types: The types to export, e.g. person, organization, people_contact_details @param from: Retrieve all files since a given data and time @param block_until_completed: Set to true to monitor the export progress @param locale: Required for translations export @raise Itrp::Exception
in case the export progress could not be monitored
# File lib/itrp/client.rb, line 156 def export(types, from = nil, block_until_completed = false, locale = nil) data = {type: [types].flatten.join(',')} data[:from] = from unless from.blank? data[:locale] = locale unless locale.blank? response = post('/export', data) if response.valid? if response.raw.code.to_s == '204' @logger.info { "No changed records for '#{data[:type]}' since #{data[:from]}." } return response end @logger.info { "Export for '#{data[:type]}' successfully queued with token '#{response[:token]}'." } end if block_until_completed raise ::Itrp::UploadFailed.new("Failed to queue '#{data[:type]}' export. #{response.message}") unless response.valid? token = response[:token] while true response = get("/export/#{token}") unless response.valid? sleep(5) response = get("/export/#{token}") # single retry to recover from a network error raise ::Itrp::Exception.new("Unable to monitor progress for '#{data[:type]}' export. #{response.message}") unless response.valid? end # wait 30 seconds while the response is OK and export is still busy break unless ['queued', 'processing'].include?(response[:state]) @logger.debug { "Export of '#{data[:type]}' is #{response[:state]}. Checking again in 30 seconds." } sleep(30) end end response end
send HTTPS GET request and return instance of Itrp::Response
# File lib/itrp/client.rb, line 97 def get(path, params = {}, header = {}) _send(Net::HTTP::Get.new(expand_path(path, params), expand_header(header))) end
upload a CSV file to import @param csv: The CSV File or the location of the CSV file @param type: The type, e.g. person, organization, people_contact_details @raise Itrp::UploadFailed
in case the file could was not accepted by ITRP and block_until_completed
is true
@raise Itrp::Exception
in case the import progress could not be monitored
# File lib/itrp/client.rb, line 122 def import(csv, type, block_until_completed = false) csv = File.open(csv, 'rb') unless csv.respond_to?(:path) && csv.respond_to?(:read) data, headers = Itrp::Multipart::Post.prepare_query(type: type, file: csv) request = Net::HTTP::Post.new(expand_path('/import'), expand_header(headers)) request.body = data response = _send(request) @logger.info { "Import file '#{csv.path}' successfully uploaded with token '#{response[:token]}'." } if response.valid? if block_until_completed raise ::Itrp::UploadFailed.new("Failed to queue #{type} import. #{response.message}") unless response.valid? token = response[:token] while true response = get("/import/#{token}") unless response.valid? sleep(5) response = get("/import/#{token}") # single retry to recover from a network error raise ::Itrp::Exception.new("Unable to monitor progress for #{type} import. #{response.message}") unless response.valid? end # wait 30 seconds while the response is OK and import is still busy break unless ['queued', 'processing'].include?(response[:state]) @logger.debug { "Import of '#{csv.path}' is #{response[:state]}. Checking again in 30 seconds." } sleep(30) end end response end
# File lib/itrp/client.rb, line 189 def logger @logger end
Retrieve an option
# File lib/itrp/client.rb, line 71 def option(key) @options[key] end
send HTTPS POST request and return instance of Itrp::Response
# File lib/itrp/client.rb, line 113 def post(path, data = {}, header = {}) _send(json_request(Net::HTTP::Post, path, data, header)) end
send HTTPS PATCH request and return instance of Itrp::Response
# File lib/itrp/client.rb, line 107 def put(path, data = {}, header = {}) _send(json_request(Net::HTTP::Patch, path, data, header)) end
Private Instance Methods
Send a request to ITRP and wrap the HTTP Response
in an Itrp::Response
Guaranteed to return a Response
, thought it may be empty?
# File lib/itrp/client.rb, line 266 def _send(request, domain = @domain, port = @port, ssl = @ssl) @logger.debug { "Sending #{request.method} request to #{domain}:#{port}#{request.path}" } _response = begin http_with_proxy = option(:proxy_host).blank? ? Net::HTTP : Net::HTTP::Proxy(option(:proxy_host), option(:proxy_port), option(:proxy_user), option(:proxy_password)) http = http_with_proxy.new(domain, port) http.read_timeout = option(:read_timeout) http.use_ssl = ssl http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @ssl_verify_none http.start{ |_http| _http.request(request) } rescue ::Exception => e Struct.new(:body, :message, :code, :header).new(nil, "No Response from Server - #{e.message} for '#{domain}:#{port}#{request.path}'", 500, {}) end response = Itrp::Response.new(request, _response) if response.valid? @logger.debug { "Response:\n#{JSON.pretty_generate(response.json)}" } elsif response.raw.body =~ /^\s*<\?xml/i @logger.debug { "XML response:\n#{response.raw.body}" } elsif '303' == response.raw.code.to_s @logger.debug { "Redirect: #{response.raw.header['Location']}" } else @logger.error { "Request failed: #{response.message}" } end response end
Expand the given header with the default header
# File lib/itrp/client.rb, line 211 def expand_header(header = {}) header = DEFAULT_HEADER.merge(header) header['X-ITRP-Account'] = option(:account) if option(:account) header['AUTHORIZATION'] = 'Basic ' + ["#{option(:api_token)}:x"].pack('m*').gsub(/\s/, '') if option(:source) header['X-ITRP-Source'] = option(:source) header['HTTP_USER_AGENT'] = option(:source) end header end
Expand one parameter, e.g. (:“created_at=>”, DateTime.now) to “created_at=%3E22011-12-16T12:24:41%2B01:00”
# File lib/itrp/client.rb, line 239 def expand_param(key, value) param = uri_escape(key.to_s).gsub('%3D', '=') # handle :"updated_at=>" or :"person_id!=" parameters param << '=' unless key['='] param << typecast(value) param end
Expand the given path with the parameters Examples:
person_id: 5 :"updated_at=>" => yesterday fields: ["id", "created_at", "sourceID"]
# File lib/itrp/client.rb, line 227 def expand_path(path, params = {}) path = path.dup path = "/#{path}" unless path =~ /^\// # make sure path starts with / path = "/#{option(:api_version)}#{path}" unless path =~ /^\/v[\d.]+\// # preprend api version params.each do |key, value| path << (path['?'] ? '&' : '?') path << expand_param(key, value) end path end
create a request (place data in body if the request becomes too large)
# File lib/itrp/client.rb, line 196 def json_request(request_class, path, data = {}, header = {}) Itrp::Attachments.new(self).upload_attachments!(path, data) request = request_class.new(expand_path(path), expand_header(header)) body = {} data.each{ |k,v| body[k.to_s] = typecast(v, false) } request.body = body.to_json request end
parse the given URI to [domain, port, ssl, path]
# File lib/itrp/client.rb, line 292 def ssl_domain_port_path(uri) uri = URI.parse(uri) ssl = uri.scheme == 'https' [ssl, uri.host, uri.port, uri.path] end
Parameter value typecasting
# File lib/itrp/client.rb, line 247 def typecast(value, escape = true) case value.class.name.to_sym when :NilClass then '' when :String then escape ? uri_escape(value) : value when :TrueClass then 'true' when :FalseClass then 'false' when :DateTime then datetime = value.new_offset(0).iso8601; escape ? uri_escape(datetime) : datetime when :Date then value.strftime("%Y-%m-%d") when :Time then value.strftime("%H:%M") # do not convert arrays in put/post requests as squashing arrays is only used in filtering when :Array then escape ? value.map{ |v| typecast(v, escape) }.join(',') : value # TODO: temporary for special constructions to update contact details, see Request #1444166 when :Hash then escape ? value.to_s : value else escape ? value.to_json : value.to_s end end
# File lib/itrp/client.rb, line 206 def uri_escape(value) URI.escape(value, URI_ESCAPE_PATTERN).gsub('.', '%2E') end