scaleapi 0.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.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "scaleapi/ruby"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,94 @@
1
+ class Scale
2
+ attr_accessor :api_key, :callback_auth_key, :default_request_params, :logging
3
+
4
+ def self.validate_api_key(api_key)
5
+ if api_key.length < 5 || !(api_key.start_with?('live') || api_key.start_with?('test'))
6
+ raise Api::APIKeyInvalid
7
+ end
8
+ end
9
+
10
+ def initialize(api_key: nil, callback_auth_key: nil, default_request_params: {}, logging: false, callback_url: nil)
11
+ Scale.validate_api_key(api_key)
12
+
13
+ self.api_key = api_key
14
+ self.callback_auth_key = callback_auth_key
15
+ self.default_request_params = default_request_params.merge(callback_url: callback_url)
16
+ self.logging = logging
17
+ end
18
+
19
+ def live?
20
+ api_key.start_with?('live')
21
+ end
22
+
23
+ def test?
24
+ api_key.start_with?('test')
25
+ end
26
+
27
+ def client
28
+ @client ||= Api.new(api_key, callback_auth_key, default_request_params, logging)
29
+ end
30
+
31
+ def tasks
32
+ @tasks ||= Scale::Api::Tasks.new(client)
33
+ end
34
+
35
+ def create_datacollection_task(args = {})
36
+ Api::Tasks::Datacollection.create(args.merge(client: client))
37
+ end
38
+
39
+ def create_categorization_task(args = {})
40
+ Api::Tasks::Categorization.create(args.merge(client: client))
41
+ end
42
+
43
+ def create_comparison_task(args = {})
44
+ Api::Tasks::Comparison.create(args.merge(client: client))
45
+ end
46
+
47
+ def create_image_recognition_task(args = {})
48
+ Api::Tasks::ImageRecognition.create(args.merge(client: client))
49
+ end
50
+
51
+ def create_phone_call_task(args = {})
52
+ Api::Tasks::PhoneCall.create(args.merge(client: client))
53
+ end
54
+
55
+ def create_transcription_task(args = {})
56
+ Api::Tasks::Transcription.create(args.merge(client: client))
57
+ end
58
+
59
+ def create_audio_transcription_task(args = {})
60
+ Api::Tasks::AudioTranscription.create(args.merge(client: client))
61
+ end
62
+
63
+ def build_callback(params, callback_key: nil)
64
+ callback = Api::Callback.new(params, callback_key: callback_key, client: client)
65
+
66
+ if block_given?
67
+ yield callback
68
+ else
69
+ callback
70
+ end
71
+ end
72
+
73
+ alias_method :live, :live?
74
+ alias_method :test, :test?
75
+ alias_method :live_mode?, :live?
76
+ alias_method :test_mode?, :test?
77
+ alias_method :create_annotation_task, :create_image_recognition_task
78
+ alias_method :create_phonecall_task, :create_phone_call_task
79
+ alias_method :create_audiotranscription_task, :create_audio_transcription_task
80
+ end
81
+
82
+ require 'scale/api'
83
+ require 'scale/api/errors'
84
+ require 'scale/api/callback'
85
+ require 'scale/api/tasks'
86
+ require 'scale/api/tasks/audio_transcription'
87
+ require 'scale/api/tasks/base_task'
88
+ require 'scale/api/tasks/datacollection'
89
+ require 'scale/api/tasks/categorization'
90
+ require 'scale/api/tasks/comparison'
91
+ require 'scale/api/tasks/image_recognition'
92
+ require 'scale/api/tasks/phone_call'
93
+ require 'scale/api/tasks/transcription'
94
+ require 'scale/api/task_list'
@@ -0,0 +1,75 @@
1
+ require 'uri'
2
+ require 'faraday'
3
+ require 'json'
4
+
5
+ class Scale
6
+ class Api < Struct.new(:api_key, :callback_auth_key, :default_request_params, :logging)
7
+ SCALE_API_URL = 'https://api.scaleapi.com/v1/'
8
+
9
+ def connection
10
+ @connection ||= Faraday.new(:url => SCALE_API_URL) do |faraday|
11
+ faraday.request :basic_auth, self.api_key, ''
12
+ faraday.request :url_encoded # form-encode POST params
13
+ faraday.response :logger if logging # log requests to STDOUT
14
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
15
+ end
16
+ end
17
+
18
+ def get(url, params = {})
19
+ response = connection.get do |req|
20
+ req.url "#{SCALE_API_URL}#{url}"
21
+ req.params.merge!(default_request_params.merge(params))
22
+ req.headers['X-API-Client'] = "Ruby"
23
+ req.headers["X-API-Client-Version"] = '0.1.0'
24
+ end
25
+
26
+ if response.status != 200
27
+ return handle_error(response)
28
+ end
29
+
30
+ response
31
+ rescue Faraday::Error::ConnectionFailed
32
+ raise Scale::Api::ConnectionError
33
+ end
34
+
35
+ def post(url, body = {})
36
+ body.delete(:callback_url) if body.keys.include?(:callback_url) && body[:callback_url].nil?
37
+ body = default_request_params.merge(body)
38
+
39
+ response = connection.post do |req|
40
+ req.url "#{SCALE_API_URL}#{url}"
41
+ req.headers['Content-Type'] = 'application/json'
42
+ req.body = body.to_json
43
+ req.headers['X-API-Client'] = "Ruby"
44
+ req.headers["X-API-Client-Version"] = '0.1.0'
45
+ end
46
+
47
+ if response.status != 200
48
+ return handle_error(response)
49
+ end
50
+
51
+ response
52
+ rescue Faraday::Error::ConnectionFailed
53
+ raise Scale::Api::ConnectionError
54
+ end
55
+
56
+ def handle_error(response)
57
+ error_body = JSON.parse(response.body)
58
+ if response.status == 404
59
+ raise Scale::Api::NotFound.new(error_body['error'], response.status)
60
+ elsif response.status == 429
61
+ raise Scale::Api::TooManyRequests.new(error_body['error'], response.status)
62
+ elsif response.status > 499
63
+ raise Scale::Api::InternalServerError.new(error_body['error'], response.status)
64
+ elsif response.status == 401
65
+ raise Scale::Api::Unauthorized.new(error_body['error'], response.status)
66
+ else
67
+ raise Scale::Api::BadRequest.new(error_body['error'], response.status)
68
+ end
69
+ rescue JSON::ParserError
70
+ raise Scale::Api::InternalServerError
71
+ end
72
+ end
73
+ end
74
+
75
+ require 'scale/api/errors'
@@ -0,0 +1,29 @@
1
+ class Scale
2
+ class Api
3
+ class Callback
4
+ attr_reader :client, :response, :task, :task_id, :request_callback_key
5
+
6
+ def initialize(params, callback_key: nil, client: nil)
7
+ @client = client
8
+ @response = params[:response]
9
+ @request_callback_key = callback_key
10
+
11
+ if params['task']
12
+ @task_id = params['task']['id']
13
+ @task = Scale::Api::Tasks::BaseTask.from_hash(params['task'].merge('client' => client))
14
+ end
15
+ end
16
+
17
+ def verified?
18
+ Callback.valid_callback_auth_key?(request_callback_key, client.callback_auth_key)
19
+ end
20
+
21
+ def self.valid_callback_auth_key?(callback_key, request_callback_key)
22
+ !!(callback_key && request_callback_key && request_callback_key == callback_key)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ require 'scale/api/tasks'
29
+ require 'scale/api/tasks/base_task'
@@ -0,0 +1,46 @@
1
+ class Scale
2
+ class Api
3
+ class Error < StandardError
4
+ attr_accessor :status_code
5
+
6
+ def initialize(message = 'Please double check the request was properly formed and try again', status_code = 400)
7
+ super(message)
8
+ self.status_code = status_code
9
+ end
10
+ end
11
+
12
+ class BadRequest < Error
13
+ end
14
+
15
+ class TooManyRequests < Error
16
+ end
17
+
18
+ class NotFound < Error
19
+ end
20
+
21
+ class Unauthorized < Error
22
+ def initialize(message = "Please ensure that the api_key provided is correct. To find your API key, go to your Scale Dashboard", status_code = 401)
23
+ super(message, status_code)
24
+ end
25
+ end
26
+
27
+ class InternalServerError < Error
28
+ def initialize(message = "Scale's servers are currently experiencing issues. Please wait a moment and try again.", status_code = 500)
29
+ super(message, status_code)
30
+ end
31
+ end
32
+
33
+ class ConnectionError < Error
34
+ def initialize(message = "There's an issue connecting to Scale's servers. Please check you're connected to the internet, and try again.'", status_code = nil)
35
+ super(message, status_code)
36
+ end
37
+ end
38
+
39
+ class APIKeyInvalid < Error
40
+ def initialize(message = "Provided api_key is invalid. Please double check you passed it in correctly and try again. If you're having trouble finding it, check your Scale Dashboard", status_code = nil)
41
+ super(message, status_code)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ require 'scale/api/tasks/base_task'
2
+ require 'scale/api/tasks/audio_transcription'
3
+ require 'scale/api/tasks/datacollection'
4
+ require 'scale/api/tasks/categorization'
5
+ require 'scale/api/tasks/comparison'
6
+ require 'scale/api/tasks/image_recognition'
7
+ require 'scale/api/tasks/phone_call'
8
+ require 'scale/api/tasks/transcription'
9
+
10
+ class Scale
11
+ class Api
12
+ class TaskList
13
+ include Enumerable
14
+ extend Forwardable
15
+ def_delegators :@docs, :each, :<<, :[], :[]=, :length, :count
16
+ attr_accessor :client, :docs, :limit, :offset, :has_more, :params
17
+ TASK_TYPES_TO_CLASSNAMES = {
18
+ 'audiotranscription' => ::Scale::Api::Tasks::AudioTranscription,
19
+ 'categorization' => ::Scale::Api::Tasks::Categorization,
20
+ 'comparison' => ::Scale::Api::Tasks::Comparison,
21
+ 'datacollection' => ::Scale::Api::Tasks::Datacollection,
22
+ 'annotation' => ::Scale::Api::Tasks::ImageRecognition,
23
+ 'phonecall' => ::Scale::Api::Tasks::PhoneCall,
24
+ 'transcription' => ::Scale::Api::Tasks::Transcription
25
+ }.freeze
26
+
27
+ def initialize(client: nil, docs: [], limit: 99, offset: 0, has_more: false, params: {})
28
+ self.client = client
29
+ self.docs = docs.map do |doc|
30
+ ::Scale::Api::Tasks::BaseTask.from_hash(doc.merge('client' => client))
31
+ end
32
+
33
+ self.limit = limit
34
+ self.offset = offset
35
+ self.has_more = has_more
36
+ self.params = params # Used to get next page
37
+ end
38
+
39
+ def has_more?
40
+ !!has_more
41
+ end
42
+
43
+ def page
44
+ (offset + (limit * 1)) / limit
45
+ end
46
+
47
+ def next_page
48
+ next_page_params = params.dup
49
+ params[:offset] = params[:limit] + params[:offset]
50
+ Scale::Api::Tasks.new(client).list(params)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ class Scale
2
+ class Api
3
+ class Tasks < Struct.new(:client)
4
+ def list(start_time: nil, end_time: nil, limit: 99, offset: 0, type: nil, status: nil)
5
+ params = {
6
+ start_time: start_time ? start_time.iso8601 : nil,
7
+ end_time: end_time ? end_time.iso8601 : nil,
8
+ limit: limit,
9
+ offset: offset,
10
+ status: status,
11
+ type: type
12
+ }
13
+
14
+ response = client.get('tasks', params)
15
+ body = JSON.parse(response.body)
16
+
17
+ TaskList.new({
18
+ client: client,
19
+ docs: body['docs'],
20
+ limit: body['limit'],
21
+ offset: body['offset'],
22
+ has_more: body['has_more'],
23
+ params: params
24
+ })
25
+ end
26
+
27
+ def find(task_id)
28
+ response = client.get("task/#{task_id}")
29
+ BaseTask.from_hash(JSON.parse(response.body).merge('client' => client))
30
+ end
31
+
32
+ def cancel(task_id)
33
+ response = client.post("task/#{task_id}/cancel")
34
+ BaseTask.from_hash(JSON.parse(response.body).merge('client' => client))
35
+ end
36
+
37
+ def create(args = {})
38
+ raise ArgumentError.new('Task type is required') if (args[:type].nil? && args['type'].nil?)
39
+ klass = ::Scale::Api::TaskList::TASK_TYPES_TO_CLASSNAMES[(args[:type] || args['type']).to_s]
40
+
41
+ unless klass
42
+ raise ArgumentError.new('Unsupported task type. Supported task types: ' + ::Scale::Api::TaskList::TASK_TYPES_TO_CLASSNAMES.keys.join(','))
43
+ end
44
+
45
+ args.delete(:type)
46
+ klass.create(args.merge(client: client))
47
+ end
48
+
49
+ alias_method :all, :list
50
+ alias_method :where, :list
51
+ end
52
+ end
53
+ end
54
+
55
+ require 'scale/api/task_list'
@@ -0,0 +1,26 @@
1
+ require 'json'
2
+ require 'scale/api/tasks/base_task'
3
+
4
+ class Scale
5
+ class Api
6
+ class Tasks
7
+ class AudioTranscription < BaseTask
8
+ CREATE_PATH = 'task/audiotranscription'.freeze
9
+
10
+ def self.create(callback_url: nil, instruction: nil, attachment_type: 'audio', attachment: nil, verbatim: false, urgency: 'day', metadata: {}, client: nil)
11
+ response = client.post(CREATE_PATH, {
12
+ callback_url: callback_url,
13
+ instruction: instruction,
14
+ attachment_type: attachment_type,
15
+ attachment: attachment,
16
+ verbatim: verbatim,
17
+ urgency: urgency,
18
+ metadata: metadata
19
+ })
20
+
21
+ AudioTranscription.new(JSON.parse(response.body))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ require 'time'
2
+
3
+ class Scale
4
+ class Api
5
+ class Tasks
6
+ class BaseTask
7
+ attr_accessor :client
8
+ ATTRIBUTES = %w(task_id type instruction params urgency response callback_url created_at status completed_at callback_succeeded_at metadata).freeze
9
+ ATTRIBUTES.each { |attr| attr_reader attr }
10
+
11
+ alias_method :id, :task_id
12
+
13
+ def self.from_hash(hash)
14
+ klass = ::Scale::Api::TaskList::TASK_TYPES_TO_CLASSNAMES[(hash[:type] || hash['type']).to_s] || self
15
+ klass.new(hash)
16
+ end
17
+
18
+ def cancel!
19
+ Tasks.new(client).cancel(id)
20
+ end
21
+
22
+ def initialize(json = {})
23
+
24
+ ATTRIBUTES.each do |attr|
25
+ instance_variable_set "@#{attr}", json[attr]
26
+ end
27
+
28
+ @client = (json[:client] || json['client'])
29
+
30
+ tweak_attributes
31
+ end
32
+
33
+ def day?
34
+ urgency == 'day'
35
+ end
36
+
37
+ def week?
38
+ urgency == 'week'
39
+ end
40
+
41
+ def immediate?
42
+ urgency == 'immediate'
43
+ end
44
+
45
+ def pending?
46
+ status == 'pending'
47
+ end
48
+
49
+ def completed?
50
+ status == 'completed'
51
+ end
52
+
53
+ def canceled?
54
+ status == 'canceled'
55
+ end
56
+
57
+ def callback_succeeded?
58
+ !!callback_succeeded_at
59
+ end
60
+
61
+ protected
62
+
63
+ def tweak_attributes
64
+ @created_at = Time.parse(created_at) rescue nil
65
+ @completed_at = Time.parse(completed_at) rescue nil
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end