crowdkit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +16 -0
  5. data/Gemfile.lock +97 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +178 -0
  8. data/Rakefile +54 -0
  9. data/VERSION +1 -0
  10. data/crowdkit.gemspec +122 -0
  11. data/lib/crowdkit.rb +77 -0
  12. data/lib/crowdkit/api.rb +171 -0
  13. data/lib/crowdkit/api/arguments.rb +187 -0
  14. data/lib/crowdkit/api/factory.rb +29 -0
  15. data/lib/crowdkit/api/request_methods.rb +70 -0
  16. data/lib/crowdkit/api/response_wrapper.rb +122 -0
  17. data/lib/crowdkit/client.rb +36 -0
  18. data/lib/crowdkit/client/account.rb +7 -0
  19. data/lib/crowdkit/client/jobs.rb +56 -0
  20. data/lib/crowdkit/client/judgments.rb +9 -0
  21. data/lib/crowdkit/client/poll.rb +48 -0
  22. data/lib/crowdkit/client/statuses.rb +14 -0
  23. data/lib/crowdkit/client/units.rb +54 -0
  24. data/lib/crowdkit/client/workers.rb +13 -0
  25. data/lib/crowdkit/client/worksets.rb +27 -0
  26. data/lib/crowdkit/config.rb +62 -0
  27. data/lib/crowdkit/core_ext/array.rb +7 -0
  28. data/lib/crowdkit/core_ext/hash.rb +30 -0
  29. data/lib/crowdkit/core_ext/sawyer.rb +7 -0
  30. data/lib/crowdkit/error.rb +198 -0
  31. data/lib/crowdkit/middleware/raise_error.rb +14 -0
  32. data/lib/crowdkit/ssl_certs/cacerts.pem +3868 -0
  33. data/lib/crowdkit/version.rb +3 -0
  34. data/spec/crowdkit/client/account_spec.rb +19 -0
  35. data/spec/crowdkit/client/jobs_spec.rb +275 -0
  36. data/spec/crowdkit/client/judgments_spec.rb +30 -0
  37. data/spec/crowdkit/client/statuses_spec.rb +30 -0
  38. data/spec/crowdkit/client/units_spec.rb +266 -0
  39. data/spec/crowdkit/client/workers_spec.rb +33 -0
  40. data/spec/crowdkit/client/worksets_spec.rb +113 -0
  41. data/spec/crowdkit/client_spec.rb +55 -0
  42. data/spec/crowdkit_spec.rb +139 -0
  43. data/spec/fixtures/account.json +7 -0
  44. data/spec/fixtures/jobs/copy.json +79 -0
  45. data/spec/fixtures/jobs/job.json +1 -0
  46. data/spec/fixtures/jobs/order.json +53 -0
  47. data/spec/fixtures/jobs/search.json +1 -0
  48. data/spec/fixtures/judgments/index.json +46 -0
  49. data/spec/fixtures/judgments/show.json +16 -0
  50. data/spec/fixtures/root.json +1 -0
  51. data/spec/fixtures/statuses/index.json +25 -0
  52. data/spec/fixtures/statuses/status.json +12 -0
  53. data/spec/fixtures/units/copy.json +34 -0
  54. data/spec/fixtures/units/index.json +1502 -0
  55. data/spec/fixtures/units/poll/normal.json +902 -0
  56. data/spec/fixtures/units/poll/small.json +602 -0
  57. data/spec/fixtures/units/show.json +34 -0
  58. data/spec/fixtures/upload.csv +25 -0
  59. data/spec/fixtures/worksets/index.json +20 -0
  60. data/spec/fixtures/worksets/show.json +9 -0
  61. data/spec/spec_helper.rb +53 -0
  62. data/spec/support/base.rb +11 -0
  63. 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,7 @@
1
+ module Crowdkit
2
+ class Client::Account < API
3
+ def get
4
+ do_get("account")
5
+ end
6
+ end
7
+ end
@@ -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,9 @@
1
+ module Crowdkit
2
+ class Client::Judgments < API
3
+ def get(*args)
4
+ arguments(args, required: [:judgment_id])
5
+ do_get("judgments/#{judgment_id}")
6
+ end
7
+ alias :find :get
8
+ end
9
+ 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,14 @@
1
+ module Crowdkit
2
+ class Client::Statuses < API
3
+ def id_key
4
+ :uuid
5
+ end
6
+
7
+ def get(*args)
8
+ arguments(args, required: [:uuid])
9
+
10
+ do_get("statuses/#{uuid}")
11
+ end
12
+ alias :find :get
13
+ end
14
+ 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