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,77 @@
1
+ require 'sawyer'
2
+ require 'crowdkit/version'
3
+ require 'crowdkit/core_ext/hash'
4
+ require 'crowdkit/core_ext/array'
5
+ require 'crowdkit/core_ext/sawyer'
6
+ require 'crowdkit/config'
7
+ require 'crowdkit/api'
8
+ require 'crowdkit/client'
9
+ require 'crowdkit/error'
10
+ require 'crowdkit/middleware/raise_error'
11
+
12
+ module Crowdkit
13
+ def self.new(overrides={}, &block)
14
+ #Use the global config if no overrides present
15
+ if @configuration && overrides.empty? && !block_given?
16
+ Client.new(@configuration)
17
+ else
18
+ Client.new(overrides, &block)
19
+ end
20
+ end
21
+
22
+ def self.last_response
23
+ @last_response
24
+ end
25
+
26
+ def self.last_response=(last_response)
27
+ @last_response = last_response
28
+ end
29
+
30
+ def self.configure(overrides={}, &block)
31
+ @configuration = Config.new(overrides, &block)
32
+ end
33
+
34
+ def self.reset!
35
+ @last_response = nil
36
+ @configuration = nil
37
+ end
38
+
39
+ # This takes a response from a resource that processes something in the
40
+ # background (i.e. a 202 response) and will ping the status API until it timesout
41
+ # an error is returned, or the background process is completed.
42
+ def self.wait_on_status(resource, max_time_to_wait_in_seconds = 30)
43
+ raise ArgumentError, "This resource did not respond with a 202 status code and has no status to wait on." unless resource.last_response.status == 202
44
+ progressbar = ProgressBar.create(total: 100)
45
+ status = resource.current_status
46
+ wait = 1
47
+
48
+ max_time_to_wait_in_seconds.times do |i|
49
+ break if status.state == "completed"
50
+ raise StatusError.new(status) if ["failed", "killed"].include?(status.state)
51
+
52
+ #increase wait time to 5 seconds after 5 seconds
53
+ wait = 5 if i == 5
54
+ #increase wait time to 10 seconds after 30 seconds
55
+ wait = 10 if i == 30
56
+
57
+ if i % wait == 0
58
+ percents = status.percent_complete.to_i
59
+ progressbar.progress = percents if percents > 0
60
+ sleep(wait)
61
+ status = resource.current_status
62
+ end
63
+ end
64
+
65
+ raise StatusError.new(status, true) unless status.state == "completed"
66
+ progressbar.finish
67
+ status
68
+ end
69
+
70
+ private
71
+
72
+ #Not convinced we even want to support this...
73
+ def self.method_missing(method_name, *args, &block)
74
+ return super unless new.respond_to?(method_name)
75
+ new.send(method_name, *args, &block)
76
+ end
77
+ end
@@ -0,0 +1,171 @@
1
+ require 'crowdkit/api/arguments'
2
+ require 'crowdkit/api/factory'
3
+ require 'crowdkit/api/request_methods'
4
+ require 'crowdkit/api/response_wrapper'
5
+
6
+ module Crowdkit
7
+ class API
8
+ extend Forwardable
9
+ include RequestMethods
10
+ attr_accessor :stored_params
11
+ attr_reader :config, :client
12
+ attr_writer :auto_pagination
13
+
14
+ #We delegate last_response to the attr_accessor of the top level Client
15
+ def_delegators :client, :last_response, :last_response=
16
+
17
+ #This should only be called by API Factory to ensure _config, and _client
18
+ #get set. We override this in Client as it's the root of the API.
19
+ def initialize(options, &block)
20
+ @config = options.delete(:_config)
21
+ @client = options.delete(:_client)
22
+ @stored_params = options
23
+ with(options)
24
+ end
25
+
26
+ def auto_pagination
27
+ @auto_pagination || @config.auto_paginate
28
+ end
29
+
30
+ # Acts as setter and getter for api requests arguments parsing.
31
+ #
32
+ # Returns Arguments instance.
33
+ #
34
+ def arguments(args=(not_set = true), options={}, &block)
35
+ if not_set
36
+ @arguments
37
+ else
38
+ @arguments = Arguments.new(self, options.symbolize_keys).parse(*args, &block)
39
+ end
40
+ end
41
+
42
+ # Set an option to a given value
43
+ #
44
+ # @param [String] option
45
+ # @param [Object] value
46
+ # @param [Boolean] ignore_setter
47
+ #
48
+ # @return [self]
49
+ #
50
+ # @api public
51
+ def set(option, value=(not_set=true), ignore_setter=false, &block)
52
+ raise ArgumentError, 'value not set' if block and !not_set
53
+ return self if !not_set and value.nil?
54
+
55
+ if not_set
56
+ set_options option
57
+ return self
58
+ end
59
+
60
+ if respond_to?("#{option}=") and not ignore_setter
61
+ return __send__("#{option}=", value)
62
+ end
63
+
64
+ define_accessors option, value
65
+ self
66
+ end
67
+
68
+ #overridden in children API's that have their own id's
69
+ def id_key
70
+ :job_id
71
+ end
72
+
73
+ # Scope for passing request required arguments.
74
+ #
75
+ def with(args)
76
+ case args
77
+ when Hash
78
+ #This happens when the job id is passed early in the chain
79
+ if (custom = args.delete(:_args)) && custom.first
80
+ stored_params[id_key] = custom.first
81
+ set id_key, custom.first
82
+ end
83
+ set args
84
+ when Fixnum, /^(\d+|([a-z])([a-z]|\d)*)$/
85
+ stored_params[id_key] = args
86
+ set id_key, args
87
+ else
88
+ ::Kernel.raise ArgumentError, 'This api does not support passed in arguments'
89
+ end
90
+ end
91
+
92
+ # Defines a namespace
93
+ #
94
+ # @param [Array[Symbol]] names
95
+ # the name for the scope
96
+ #
97
+ # @return [self]
98
+ #
99
+ # @api public
100
+ def self.namespace(*names)
101
+ options = names.last.is_a?(Hash) ? names.pop : {}
102
+ names = names.map(&:to_sym)
103
+ name = names.pop
104
+ return if public_method_defined?(name)
105
+
106
+ class_name = extract_class_name(name, options)
107
+ define_method(name) do |*args, &block|
108
+ options = args.last.is_a?(Hash) ? args.pop : {}
109
+ options[:_args] = args
110
+ options[:_config] = @config
111
+ options[:_client] = @client
112
+ API::Factory.new(class_name, stored_params.merge(options), &block)
113
+ end
114
+ self
115
+ end
116
+
117
+ # Extracts class name from options
118
+ #
119
+ # @return [String]
120
+ #
121
+ # @api private
122
+ def self.extract_class_name(name, options)
123
+ if !options[:class_name]
124
+ converted = options.fetch(:full_name, name).to_s
125
+ converted = converted.split('_').map(&:capitalize).join
126
+ class_name = options.fetch(:root, false) ? '': "#{self.name}::"
127
+ class_name += converted
128
+ class_name
129
+ else
130
+ options[:class_name]
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ # Set multiple options
137
+ #
138
+ # @api private
139
+ def set_options(options)
140
+ unless options.respond_to?(:each)
141
+ raise ArgumentError, 'cannot iterate over value'
142
+ end
143
+ options.each { |key, value| set(key, value) }
144
+ end
145
+
146
+ # Define setters and getters
147
+ #
148
+ # @api private
149
+ def define_accessors(option, value)
150
+ setter = proc { |val| set option, val, true }
151
+ getter = proc { value }
152
+
153
+ define_singleton_method("#{option}=", setter) if setter
154
+ define_singleton_method(option, getter) if getter
155
+ end
156
+
157
+ # Dynamically define a method for setting request option
158
+ #
159
+ # @api private
160
+ def define_singleton_method(method_name, content=Proc.new)
161
+ (class << self; self; end).class_eval do
162
+ undef_method(method_name) if method_defined?(method_name)
163
+ if String === content
164
+ class_eval("def #{method_name}() #{content}; end")
165
+ else
166
+ define_method(method_name, &content)
167
+ end
168
+ end
169
+ end
170
+ end # API
171
+ end # Crowdkit
@@ -0,0 +1,187 @@
1
+ module Crowdkit
2
+ class API
3
+ # Request arguments handler
4
+ class Arguments
5
+ AUTO_PAGINATION = :auto_pagination
6
+
7
+ # Parameters passed to request
8
+ attr_reader :params
9
+
10
+ attr_reader :remaining
11
+
12
+ # The request api
13
+ #
14
+ attr_reader :api
15
+
16
+ # Required arguments
17
+ #
18
+ attr_reader :required
19
+ private :required
20
+
21
+ # Optional arguments
22
+ #
23
+ attr_reader :optional
24
+ private :optional
25
+
26
+ # Takes api, filters and required arguments
27
+ #
28
+ # = Parameters
29
+ # :required - arguments that must be present before request is fired
30
+ #
31
+ def initialize(api, options={})
32
+ @api = api
33
+ @required = options.fetch(:required, []).map(&:to_s)
34
+ @optional = options.fetch(:optional, []).map(&:to_s)
35
+ end
36
+
37
+ # Parse arguments to allow for flexible api calls.
38
+ # Arguments can be part of parameters hash or be simple string arguments.
39
+ #
40
+ def parse(*args, &block)
41
+ options = args.extract_options!
42
+
43
+ if !args.empty? && ![File, Array].include?(args.last.class)
44
+ parse_arguments *args
45
+ else
46
+ # Arguments are inside the parameters hash
47
+ parse_options options
48
+ end
49
+
50
+ @params = options
51
+ @remaining = extract_remaining(args)
52
+ extract_pagination(options)
53
+ yield_or_eval(&block)
54
+ self
55
+ end
56
+
57
+ # Check if required keys are present inside parameters hash.
58
+ #
59
+ def assert_required(required)
60
+ assert_required_keys required, params
61
+ self
62
+ end
63
+
64
+ # Check if parameters match expected values.
65
+ #
66
+ def assert_values(values, key=nil)
67
+ assert_valid_values values, (key.nil? ? params : params[key])
68
+ self
69
+ end
70
+
71
+ private
72
+
73
+ # Check and set all requried arguments.
74
+ #
75
+ def parse_arguments(*args)
76
+ assert_presence_of *args
77
+ required.each_with_index do |req, indx|
78
+ api.set req, args[indx]
79
+ end
80
+ check_requirement!(*args)
81
+ end
82
+
83
+ # Find remaining arguments
84
+ #
85
+ def extract_remaining(args)
86
+ args[required.size..-1]
87
+ end
88
+
89
+ # Fine auto_pagination parameter in options hash
90
+ #
91
+ def extract_pagination(options)
92
+ if (value = options.delete(AUTO_PAGINATION))
93
+ api.client.auto_pagination = value
94
+ end
95
+ end
96
+
97
+ # Remove required arguments from parameters and
98
+ # validate their presence(if not nil or empty string).
99
+ #
100
+ def parse_options(options)
101
+ options.each { |key, val| remove_required(options, key, val) }
102
+ provided_args = check_assignment!(options)
103
+ check_requirement!(*provided_args.keys)
104
+ end
105
+
106
+ # Remove required argument from parameters
107
+ #
108
+ def remove_required(options, key, val)
109
+ key = key.to_s
110
+ if required.include? key
111
+ assert_presence_of val
112
+ options.delete key.intern
113
+ api.set key, val
114
+ end
115
+ end
116
+
117
+ # Check if required arguments have been set on instance.
118
+ #
119
+ def check_assignment!(options)
120
+ result = required.inject({}) { |hash, arg|
121
+ if api.respond_to?(:"#{arg}")
122
+ hash[arg] = api.send(:"#{arg}")
123
+ else
124
+ hash[arg] = nil
125
+ end
126
+ hash
127
+ }
128
+ assert_presence_of result
129
+ result
130
+ end
131
+
132
+ # Check if required arguments are present.
133
+ #
134
+ def check_requirement!(*args)
135
+ args_length = args.length
136
+ required_length = required.length
137
+
138
+ if args_length < required_length
139
+ ::Kernel.raise ArgumentError, "wrong number of arguments (#{args_length} for #{required_length})"
140
+ end
141
+ end
142
+
143
+ # Ensure that esential arguments are present before request is made.
144
+ #
145
+ # == Parameters
146
+ # Hash/Array of arguments to be checked against nil and empty string
147
+ #
148
+ # == Example
149
+ # assert_presence_of user: '...', repo: '...'
150
+ # assert_presence_of user, repo
151
+ #
152
+ def assert_presence_of(*args)
153
+ hash = args.last.is_a?(::Hash) ? args.pop : {}
154
+
155
+ errors = hash.select { |key, val| val.to_s.empty? }
156
+ raise Crowdkit::ValidationError.new(errors) unless errors.empty?
157
+
158
+ args.each do |arg|
159
+ raise ArgumentError, "parameter cannot be nil" if arg.nil?
160
+ end
161
+ end
162
+
163
+ # Validate all keys present in a provided hash against required set,
164
+ # on mismatch raise CrowdKit::Error::RequiredParams
165
+ # Note that keys need to be in the same format i.e. symbols or strings,
166
+ # otherwise the comparison will fail.
167
+ #
168
+ def assert_required_keys(required, provided)
169
+ result = required.all? do |key|
170
+ provided.deep_key? key
171
+ end
172
+ if !result
173
+ raise CrowdKit::RequiredParams.new(provided, required)
174
+ end
175
+ result
176
+ end
177
+
178
+ # Evaluate block
179
+ #
180
+ def yield_or_eval(&block)
181
+ return unless block
182
+ block.arity > 0 ? yield(self) : self.instance_eval(&block)
183
+ end
184
+
185
+ end # Arguments
186
+ end # Api
187
+ end # Crowdkit
@@ -0,0 +1,29 @@
1
+ module Crowdkit
2
+ class API
3
+ class Factory
4
+
5
+ # Instantiates a new CrowdFlower api object
6
+ #
7
+ def self.new(klass, options={}, &block)
8
+ return create_instance(klass, options, &block) if klass
9
+ raise ArgumentError, 'must provide API class to be instantiated'
10
+ end
11
+
12
+ # Passes configuration options to instantiated class
13
+ #
14
+ def self.create_instance(klass, options, &block)
15
+ options.symbolize_keys!
16
+ convert_to_constant(klass.to_s).new options, &block
17
+ end
18
+
19
+ # Convert name to constant
20
+ #
21
+ def self.convert_to_constant(classes)
22
+ classes.split('::').inject(Crowdkit) do |constant, klass|
23
+ constant.const_get klass
24
+ end
25
+ end
26
+
27
+ end # Factory
28
+ end # Api
29
+ end # Crowdkit