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