crowdkit 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.
- 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
|