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