crowdkit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +97 -0
- data/LICENSE.txt +20 -0
- data/README.md +178 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/crowdkit.gemspec +122 -0
- data/lib/crowdkit.rb +77 -0
- data/lib/crowdkit/api.rb +171 -0
- data/lib/crowdkit/api/arguments.rb +187 -0
- data/lib/crowdkit/api/factory.rb +29 -0
- data/lib/crowdkit/api/request_methods.rb +70 -0
- data/lib/crowdkit/api/response_wrapper.rb +122 -0
- data/lib/crowdkit/client.rb +36 -0
- data/lib/crowdkit/client/account.rb +7 -0
- data/lib/crowdkit/client/jobs.rb +56 -0
- data/lib/crowdkit/client/judgments.rb +9 -0
- data/lib/crowdkit/client/poll.rb +48 -0
- data/lib/crowdkit/client/statuses.rb +14 -0
- data/lib/crowdkit/client/units.rb +54 -0
- data/lib/crowdkit/client/workers.rb +13 -0
- data/lib/crowdkit/client/worksets.rb +27 -0
- data/lib/crowdkit/config.rb +62 -0
- data/lib/crowdkit/core_ext/array.rb +7 -0
- data/lib/crowdkit/core_ext/hash.rb +30 -0
- data/lib/crowdkit/core_ext/sawyer.rb +7 -0
- data/lib/crowdkit/error.rb +198 -0
- data/lib/crowdkit/middleware/raise_error.rb +14 -0
- data/lib/crowdkit/ssl_certs/cacerts.pem +3868 -0
- data/lib/crowdkit/version.rb +3 -0
- data/spec/crowdkit/client/account_spec.rb +19 -0
- data/spec/crowdkit/client/jobs_spec.rb +275 -0
- data/spec/crowdkit/client/judgments_spec.rb +30 -0
- data/spec/crowdkit/client/statuses_spec.rb +30 -0
- data/spec/crowdkit/client/units_spec.rb +266 -0
- data/spec/crowdkit/client/workers_spec.rb +33 -0
- data/spec/crowdkit/client/worksets_spec.rb +113 -0
- data/spec/crowdkit/client_spec.rb +55 -0
- data/spec/crowdkit_spec.rb +139 -0
- data/spec/fixtures/account.json +7 -0
- data/spec/fixtures/jobs/copy.json +79 -0
- data/spec/fixtures/jobs/job.json +1 -0
- data/spec/fixtures/jobs/order.json +53 -0
- data/spec/fixtures/jobs/search.json +1 -0
- data/spec/fixtures/judgments/index.json +46 -0
- data/spec/fixtures/judgments/show.json +16 -0
- data/spec/fixtures/root.json +1 -0
- data/spec/fixtures/statuses/index.json +25 -0
- data/spec/fixtures/statuses/status.json +12 -0
- data/spec/fixtures/units/copy.json +34 -0
- data/spec/fixtures/units/index.json +1502 -0
- data/spec/fixtures/units/poll/normal.json +902 -0
- data/spec/fixtures/units/poll/small.json +602 -0
- data/spec/fixtures/units/show.json +34 -0
- data/spec/fixtures/upload.csv +25 -0
- data/spec/fixtures/worksets/index.json +20 -0
- data/spec/fixtures/worksets/show.json +9 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/base.rb +11 -0
- metadata +218 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class API
|
3
|
+
module RequestMethods
|
4
|
+
# Make an HTTP request
|
5
|
+
#
|
6
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
7
|
+
# @param options [Hash] Query and header params for request
|
8
|
+
# @return [Sawyer::Resource]
|
9
|
+
[:get,:post,:put,:patch,:delete,:head].each do |method|
|
10
|
+
define_method("do_#{method}") do |url, data = {}, options = {}|
|
11
|
+
request method, url, data, options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def agent
|
16
|
+
raise Crowdkit::Unauthorized unless config.access_token
|
17
|
+
@agent ||= Sawyer::Agent.new(config.api_endpoint, sawyer_options)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def request(method, path, data, options = {})
|
23
|
+
if data.is_a?(Hash)
|
24
|
+
options[:headers] = data.delete(:headers) || {}
|
25
|
+
options[:headers][:accept] = accept if accept = data.delete(:accept)
|
26
|
+
options[:query] = data.delete(:query) || {}
|
27
|
+
if [:get, :head, :delete].include?(method)
|
28
|
+
data.each do |key, _|
|
29
|
+
options[:query][key] = data.delete(key)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
options[:headers]['Content-Type'] = 'application/json'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
Crowdkit.last_response = self.last_response = agent.call(method, URI::Parser.new.escape(path.to_s), data, options)
|
38
|
+
@retried = false
|
39
|
+
ResponseWrapper.new(self.last_response, self.client)
|
40
|
+
rescue Crowdkit::ServiceError => e
|
41
|
+
Crowdkit.last_response = self.last_response = Sawyer::Response.new(agent, e.response)
|
42
|
+
|
43
|
+
# The per second limit was exceeded, let's try again in a second
|
44
|
+
if e.response.status == 429 && !@retried
|
45
|
+
sleep(1)
|
46
|
+
@retried = true
|
47
|
+
retry
|
48
|
+
end
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Executes the request, checking if it was successful
|
54
|
+
#
|
55
|
+
# @return [Boolean] True on success, false otherwise
|
56
|
+
def boolean_from_response(method, path, options = {})
|
57
|
+
response = request(method, path, options)
|
58
|
+
response.status == 204
|
59
|
+
rescue Crowdkit::NotFound
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def sawyer_options
|
64
|
+
conn_opts = config.connection_options
|
65
|
+
conn_opts[:builder] = config.stack
|
66
|
+
{ faraday: Faraday.new(conn_opts) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class API
|
3
|
+
class ResponseWrapper
|
4
|
+
attr_reader :sawyer, :client
|
5
|
+
extend Forwardable
|
6
|
+
include RequestMethods
|
7
|
+
|
8
|
+
def_delegators :data, :empty?, :size, :include?, :length, :first, :last, :flatten, :include?, :keys, :to_s, :to_hash, :rels
|
9
|
+
def_delegators :client, :last_response, :last_response=, :config, :auto_pagination
|
10
|
+
def_delegators :each, :to_a, :to_ary, :to_enum
|
11
|
+
|
12
|
+
def initialize(sawyer, client)
|
13
|
+
@sawyer = sawyer
|
14
|
+
@client = client
|
15
|
+
end
|
16
|
+
|
17
|
+
def data
|
18
|
+
@sawyer.data
|
19
|
+
end
|
20
|
+
|
21
|
+
def headers
|
22
|
+
@sawyer.headers
|
23
|
+
end
|
24
|
+
|
25
|
+
# Lookup an attribute from the body if hash, otherwise behave like array index.
|
26
|
+
# Convert any key to string before calling.
|
27
|
+
#
|
28
|
+
def [](key)
|
29
|
+
if self.data.is_a?(Array)
|
30
|
+
self.data[key]
|
31
|
+
else
|
32
|
+
self.data.send(:"#{key}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_status
|
37
|
+
if self.data["current_status"]
|
38
|
+
do_get(self.data["current_status"].rels["self"].href)
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Iterate over each resource inside the body
|
45
|
+
#
|
46
|
+
def each(to_enum = true, &block)
|
47
|
+
body_parts = self.data.respond_to?(:each) ? self.data : [self.data]
|
48
|
+
if block_given?
|
49
|
+
body_parts.each &block
|
50
|
+
if self.auto_pagination && self.has_next_page?
|
51
|
+
next_page.each &block
|
52
|
+
end
|
53
|
+
else
|
54
|
+
res = self
|
55
|
+
if self.auto_pagination && res.has_next_page?
|
56
|
+
res = next_page
|
57
|
+
body_parts.concat(res.each(false))
|
58
|
+
end
|
59
|
+
result = to_enum ? body_parts.to_enum : body_parts
|
60
|
+
end
|
61
|
+
#Reset auto pagination option
|
62
|
+
client.auto_pagination = nil
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
[:next, :last, :first, :prev].each do |rel|
|
67
|
+
define_method("#{rel}_page") do
|
68
|
+
@sawyer.rels[rel] && do_get(@sawyer.rels[rel].href)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def has_next_page?
|
73
|
+
@sawyer.rels[:next]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Overwrite methods to hash keys
|
77
|
+
#
|
78
|
+
['id', 'type'].each do |method_name|
|
79
|
+
define_method(method_name) do
|
80
|
+
self.[](method_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def rate_limit_remaining
|
85
|
+
self.headers["X-RateLimit-Remaining"]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Check if body has an attribute
|
89
|
+
#
|
90
|
+
def has_key?(key)
|
91
|
+
res = self.data.is_a?(Array) ? self.data.first : self.data
|
92
|
+
res.key?(key.to_sym)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Coerce any method calls for body attributes
|
96
|
+
#
|
97
|
+
def method_missing(method_name, *args, &block)
|
98
|
+
if self.has_key?(method_name)
|
99
|
+
self.[](method_name, &block)
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Check if method is defined on the body
|
106
|
+
#
|
107
|
+
def respond_to?(method_name)
|
108
|
+
if self.has_key?(method_name)
|
109
|
+
true
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Print only response body
|
116
|
+
#
|
117
|
+
def inspect
|
118
|
+
"#<#{self.class.name} @data=\"#{self.data}\">"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
#The top level client, all API access starts here. It's unlike the other
|
3
|
+
#subclasses of API as it overrides the initialize method and sets us up for
|
4
|
+
#chaining.
|
5
|
+
class Client < API
|
6
|
+
attr_accessor :last_response
|
7
|
+
|
8
|
+
#Accepts a hash of configuration overrides or a Config object
|
9
|
+
def initialize(overrides, &block)
|
10
|
+
@config = overrides.is_a?(Config) ? overrides : Config.new(overrides, &block)
|
11
|
+
@client = self
|
12
|
+
@stored_params = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def root
|
16
|
+
do_get("")
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :workers
|
20
|
+
namespace :worksets
|
21
|
+
namespace :jobs
|
22
|
+
namespace :units
|
23
|
+
namespace :statuses
|
24
|
+
namespace :judgments
|
25
|
+
namespace :account
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'crowdkit/client/workers'
|
30
|
+
require 'crowdkit/client/worksets'
|
31
|
+
require 'crowdkit/client/jobs'
|
32
|
+
require 'crowdkit/client/units'
|
33
|
+
require 'crowdkit/client/statuses'
|
34
|
+
require 'crowdkit/client/judgments'
|
35
|
+
require 'crowdkit/client/poll'
|
36
|
+
require 'crowdkit/client/account'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class Client::Jobs < API
|
3
|
+
def create(*args)
|
4
|
+
arguments(args, required: [:title])
|
5
|
+
|
6
|
+
do_post("jobs", {title: title}.merge(arguments.params))
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(*args)
|
10
|
+
arguments(args, required: [:job_id])
|
11
|
+
|
12
|
+
do_get("jobs/#{job_id}")
|
13
|
+
end
|
14
|
+
alias :find :get
|
15
|
+
|
16
|
+
def search(*args)
|
17
|
+
arguments(args)
|
18
|
+
params = arguments.params
|
19
|
+
params[:query] = args.first if args.first.is_a?(String)
|
20
|
+
|
21
|
+
response = do_get("jobs", query: arguments.params)
|
22
|
+
return response unless block_given?
|
23
|
+
response.each { |el| yield el }
|
24
|
+
end
|
25
|
+
alias :list :search
|
26
|
+
alias :all :search
|
27
|
+
|
28
|
+
[:statuses, :judgments].each do |method|
|
29
|
+
define_method(method) do |*args|
|
30
|
+
arguments(args, required: [:job_id])
|
31
|
+
do_get("jobs/#{job_id}/#{method}", query: arguments.params)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def copy(*args)
|
36
|
+
arguments(args, required: [:job_id])
|
37
|
+
|
38
|
+
do_post("jobs/#{job_id}/copy", query: arguments.params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def order(*args)
|
42
|
+
arguments(args, required: [:job_id])
|
43
|
+
|
44
|
+
do_post("jobs/#{job_id}/order", query: arguments.params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def upload(*args)
|
48
|
+
arguments(args, required: [:job_id]) do
|
49
|
+
@params = args.last.read if args.last.is_a?(File)
|
50
|
+
end
|
51
|
+
do_put("jobs/#{job_id}/upload", arguments.params, headers: {'Content-Type' => 'text/csv'})
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace :units, class_name: "Client::Units"
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class Client::Poll < API
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(options, &block)
|
6
|
+
super(options, &block)
|
7
|
+
arguments([options], required: [:job_id])
|
8
|
+
|
9
|
+
prepare_sawyer
|
10
|
+
get_units
|
11
|
+
|
12
|
+
@units.data.each do |unit|
|
13
|
+
def unit.delete
|
14
|
+
do_delete("units/#{id}/queue")
|
15
|
+
end
|
16
|
+
end unless @units.data.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
@units.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def any?
|
24
|
+
last_response.status != 304
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, *args, &block)
|
28
|
+
@units.send(method_name, *args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(*args)
|
32
|
+
arguments(args, required: [:job_id])
|
33
|
+
do_delete("jobs/#{job_id}/units/queue", {ids: @units.data.map(&:id)}) if any?
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def get_units
|
39
|
+
@units = do_get("jobs/#{job_id}/units/queue", query: arguments.params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def prepare_sawyer
|
43
|
+
config_link = config
|
44
|
+
Sawyer::Resource.send(:include, RequestMethods)
|
45
|
+
Sawyer::Resource.send(:define_method, :config) { config_link }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class Client::Units < API
|
3
|
+
def id_key
|
4
|
+
:unit_id
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(*args)
|
8
|
+
arguments(args, required: [:unit_id])
|
9
|
+
|
10
|
+
do_get("units/#{unit_id}")
|
11
|
+
end
|
12
|
+
alias :find :get
|
13
|
+
|
14
|
+
def list(*args)
|
15
|
+
arguments(args, required: [:job_id])
|
16
|
+
|
17
|
+
do_get("jobs/#{job_id}/units", arguments.params)
|
18
|
+
end
|
19
|
+
alias :all :list
|
20
|
+
|
21
|
+
def create(*args)
|
22
|
+
arguments(args, required: [:job_id]) do
|
23
|
+
@params = args.last if args.last.is_a?(Array)
|
24
|
+
end
|
25
|
+
|
26
|
+
do_post("jobs/#{job_id}/units", arguments.params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def copy(*args)
|
30
|
+
arguments(args, required: [:unit_id, :destination_job_id])
|
31
|
+
|
32
|
+
do_post("units/#{unit_id}/copy", query: {destination_job_id: destination_job_id})
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(*args)
|
36
|
+
arguments(args, required: [:unit_id])
|
37
|
+
do_delete("units/#{unit_id}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def judgments(*args)
|
41
|
+
arguments(args, required: [:unit_id])
|
42
|
+
do_get("units/#{unit_id}/judgments", query: arguments.params)
|
43
|
+
end
|
44
|
+
|
45
|
+
[:pause, :resume].each do |method|
|
46
|
+
define_method(method) do |*args|
|
47
|
+
arguments(args, required: [:unit_id])
|
48
|
+
do_put("units/#{unit_id}/#{method}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
namespace :poll, class_name: 'Client::Poll'
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class Client::Workers < API
|
3
|
+
def flag(*args)
|
4
|
+
arguments(args, required: [:worker_id, :reason])
|
5
|
+
do_put("workers/#{worker_id}/flag", {reason: reason}.merge(arguments.params))
|
6
|
+
end
|
7
|
+
|
8
|
+
def deflag(*args)
|
9
|
+
arguments(args, required: [:worker_id])
|
10
|
+
do_put("workers/#{worker_id}/deflag", arguments.params)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Crowdkit
|
2
|
+
class Client::Worksets < API
|
3
|
+
def get(*args)
|
4
|
+
arguments(args, required: [:workset_id])
|
5
|
+
do_get("worksets/#{workset_id}")
|
6
|
+
end
|
7
|
+
alias :find :get
|
8
|
+
|
9
|
+
def list(*args)
|
10
|
+
arguments(args, required: [:job_id])
|
11
|
+
do_get("jobs/#{job_id}/worksets", arguments.params)
|
12
|
+
end
|
13
|
+
alias :all :list
|
14
|
+
|
15
|
+
def bonus(*args)
|
16
|
+
arguments(args, required: [:workset_id, :amount])
|
17
|
+
do_put("worksets/#{workset_id}/bonus", {amount: amount}.merge(arguments.params))
|
18
|
+
end
|
19
|
+
|
20
|
+
[:flag, :deflag, :reject, :approve].each do |method|
|
21
|
+
define_method(method) do |*args|
|
22
|
+
arguments(args, required: [:workset_id, :reason])
|
23
|
+
do_put("worksets/#{workset_id}/#{method}", {reason: reason}.merge(arguments.params))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|