matroid 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e311cca9b0b9f72f9994186dfeb1a76e952fc41f
4
- data.tar.gz: e5b62490467df16bc98777257a1a6ecf5f43667e
3
+ metadata.gz: c7bcfdbc2cb07e551d37703c5a5468151c64b4df
4
+ data.tar.gz: b5375b70f1fd8e3e7fb6575f72ba6befde09764c
5
5
  SHA512:
6
- metadata.gz: 3707dda8b144fcaa1655d72521b6cb62c13299317de4b72694d42b9cea102dfb750b40994453e154e414af1f1d80bb0ed0c20685b95886c751664c1041e6c05f
7
- data.tar.gz: cfa3cf1bc429630535df13f89914b2cc890bd838e0d650f2dcf59f4bb2d6ebe5bd6c276d1aa9ac0cb32e171aa7e945b5d85acfdb3dc92d074957963d40528194
6
+ metadata.gz: d1185974ff919bd69527b1fdbc361578af8aef7ae1551e7742ab088c2740361f027f44ee982877741a65a1bafb08de0dcc12c32c9bba3c4c86c9273f92d05d83
7
+ data.tar.gz: ddafcc63bcce5582ee7a3a71d13787cfc9ce70c562466db077ce220fcd588e33ba312554862f330c64044cec4ad4a50830e0def32d220598bfd69910a45f6dc2
data/.gitignore CHANGED
@@ -7,3 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
11
+ *.rbc
12
+ .rspec
13
+ .env
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
-
3
2
  # Specify your gem's dependencies in matroid.gemspec
4
3
  gemspec
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
- # Matroid
2
-
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/matroid`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
1
+ ## Features
2
+ * API endpoint coverage
3
+ * Automatic authenticated work flow
4
+ * [Full documentation](http://www.rubydoc.info/github/matroid/matroid-ruby)
6
5
 
7
6
  ## Installation
8
7
 
@@ -21,11 +20,116 @@ Or install it yourself as:
21
20
  $ gem install matroid
22
21
 
23
22
  ## Usage
23
+ This API wrapper allows you to easily create and use Matroid detectors for classifying various media.
24
+ It is designed to allow you to use detectors without any notion of the API.
25
+
26
+ Check the [documentation](http://www.rubydoc.info/github/matroid/matroid-ruby) for the complete reference of available methods.
27
+
28
+ # Authenticate your session
29
+ The Matroid API relies on the use of access tokens.
30
+ The easiest way to automatically authenticate your usage and handle access tokens
31
+ is to declare `MATROID_CLIENT_ID` and `MATROID_CLIENT_SECRET` in your environment.
32
+ For example, place the following in your `.env` file (this library includes the `dotenv` gem):
33
+ ```
34
+ MATROID_CLIENT_ID=XXXXXXXXXXXXXXXXXXX
35
+ MATROID_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXX
36
+ ```
37
+ Also, you may call `Matroid.authenticate(MATROID_CLIENT_ID, MATROID_CLIENT_SECRET)` before
38
+ any other methods and the token will be stored in the instance and refreshed as needed.
39
+
40
+ # Example API usage
41
+ ### Authentication
42
+ ```ruby
43
+ require 'matroid'
44
+
45
+ MATROID_CLIENT_ID=XXXXXXXXXXXXXXXXXXX
46
+ MATROID_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXX
47
+
48
+ Matroid.authenticate(MATROID_CLIENT_ID, MATROID_CLIENT_SECRET)
49
+
50
+ # Check user account info like Matroid Credits balance
51
+ Matroid.account_info
52
+ ```
53
+
54
+ ### Query for Detectors
55
+ ```ruby
56
+ # Get detector by id
57
+ detector = Matroid::Detector.find_by_id('5893f98530c1c00d0063835b')
58
+
59
+ # Get detectors by query
60
+ # Search by one or more of :labels, :name, :state, :id, :permission_level, :owner, :detector_type
61
+ # :labels and :name queries can be String or Regexp
62
+ detector = Matroid::Detector.find(id: '5893f98530c1c00d0063835b').first
63
+ cat_detectors = Matroid::Detector.find(name: 'cat')
64
+ cat_detector_id = Matroid::Detector.find(labels: 'cat', owner: true, state: 'trained').first.id
24
65
 
25
- Check your account status on Matroid with this client. More functionality coming soon.
66
+ # Find published detectors
67
+ cat_detectors = Matroid::Detector.find(name: 'cat', published: true)
68
+ cat_detector_id = Matroid::Detector.find(labels: 'cat', state: 'trained', published: true).first.id
69
+
70
+ # convenience methods
71
+ # .find_by_id(String)
72
+ # .find_one(Hash)
73
+ # .find_by_<attribute>(String)
74
+ # .find_one_by_<attribute>(String)
75
+
76
+ # View cached detectors from previous searches
77
+ all_detectors = Matroid::Detector.cached
78
+ ```
79
+
80
+ ### Use Detector class methods
81
+ ```ruby
82
+ # Get detector details
83
+ detector.to_hash #=> Hash of all the details (or you can get them separately as below)
84
+ detector.info #=> displays detector attributes in a nice printout
85
+
86
+ detector.id #=> "5893f98530c1c00d0063835b"
87
+ detector.name #=> "My cool detector"
88
+ detector.state #=> "trained"
89
+ detector.labels #=> ["label 1", "label 2", ...]
90
+ detector.permission_level #=> "private"
91
+ detector.owner #=> true
92
+ detector.training #=> "successful"
93
+ detector.type #=> "object", "face", "facial_characteristics"
94
+
95
+ # Create a detector
96
+ detector = Matroid::Detector.create('PATH/TO/ZIP/FILE', 'My awesome detector', 'general') # uploads labels and images
97
+ detector.id #=> "XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx"
98
+ detector.name #=> "My awesome detector"
99
+ detector.state #=> "pending"
100
+ detector.labels #=> ["label 1", "label 2", ...]
101
+ detector.permission_level #=> "private"
102
+ detector.owner #=> true
103
+ detector.type #=> "general"
104
+ detector.train # submits the detector for training
105
+ # You can repeatedly call detector.info to get the updates on the training
106
+
107
+ # Use a detector
108
+ detector = Matroid::Detector.find_by_id('5893f98530c1c00d0063835b')
109
+
110
+ # Classifying an image returns a hash of the detected labels (with probabilities)
111
+ # along with bounding box information (if applicable)
112
+ image_file_path = 'PATH/TO/IMAGE/FILE'
113
+ image_url = 'https://www.example.com/images/some_image.jpg'
114
+ detector.classify_image_url(image_url)
115
+ detector.classify_image_file(image_file_path)
116
+
117
+ # Classifying a video returns a hash { "video_id" => "dfoguhd078yd7dg87dfvsdf7" }
118
+ # which can later be used to check on the classification.
119
+ # A video takes some time to classify depending on the length and size of the video uploaded.
120
+ youtube_url = 'https://www.youtube.com/watch?v=0qVOUD76JOg'
121
+ video_file_path = 'PATH/TO/VIDEO/FILE'
122
+ detector.classify_video_url(youtube_url)
123
+ detector.classify_video_file(video_file_path)
124
+
125
+ # Call the following repeatedly to check the progress on the video classification results
126
+ detector_id = 'dfoguhd078yd7dg87dfvsdf7'
127
+ Matroid.get_video_results(detector_id) #=> details of timestamps with labels, etc.
128
+
129
+ ```
130
+
131
+ More functionality coming soon.
26
132
 
27
133
  ## Development
28
134
 
29
135
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
@@ -0,0 +1,148 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'httpclient/webagent-cookie' # stops warning found here: https://github.com/nahi/httpclient/issues/252
4
+ require 'httpclient'
5
+ require 'date'
6
+
7
+ BASE_API_URI = 'https://www.matroid.com/api/0.1/'
8
+ DEFAULT_GRANT_TYPE = 'client_credentials'
9
+ TOKEN_RESOURCE = 'oauth/token'
10
+ VERBS = %w(get post)
11
+
12
+ module Matroid
13
+
14
+ # @attr_reader [Token] The current stored token object
15
+ class << self
16
+ attr_reader :token, :base_api_uri, :client
17
+
18
+ # Changes the default base api uri. This is used primarily for testing purposes.
19
+ # @param uri [String]
20
+ def set_base_uri(uri)
21
+ @base_api_uri = uri
22
+ end
23
+
24
+ VERBS.each do |verb|
25
+ define_method verb do |endpoint, *params|
26
+ send_request(verb, endpoint, *params)
27
+ end
28
+ end
29
+
30
+ def parse_response(response)
31
+ if valid_json?(response.body)
32
+ status = response.status_code
33
+ parsed_response = JSON.parse(response.body)
34
+ if status != 200
35
+ err_msg = JSON.pretty_generate(parsed_response)
36
+ raise Error::RateLimitError.new(err_msg) if status == 429
37
+ raise Error::InvalidQueryError.new(err_msg) if status == 422
38
+ raise Error::PaymentError.new(err_msg) if status == 402
39
+ raise Error::ServerError.new(err_msg) if status / 100 == 5
40
+ raise Error::APIError.new(err_msg)
41
+ end
42
+
43
+ parsed_response
44
+ else
45
+ p response
46
+ raise Error::APIError.new(response.body)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def get_token(client_id, client_secret)
53
+ @base_api_uri = BASE_API_URI if @base_api_uri.nil?
54
+ url = @base_api_uri + TOKEN_RESOURCE
55
+ params = auth_params(client_id, client_secret)
56
+ @client = HTTPClient.new
57
+ response = @client.post(url, body: params)
58
+ return false unless response.status_code == 200
59
+ @token = Token.new(JSON.parse(response.body))
60
+ @client.base_url = @base_api_uri
61
+ @client.default_header = { 'Authorization' => @token.authorization_header }
62
+ end
63
+
64
+ def send_request(verb, path, params = {})
65
+ path = URI.escape(path)
66
+
67
+ # refreshes token with each call
68
+ authenticate
69
+
70
+ case verb
71
+ when 'get'
72
+ response = @client.get(path, body: params)
73
+ when 'post'
74
+ response = @client.post(path, body: params)
75
+ end
76
+
77
+ parse_response(response)
78
+ end
79
+
80
+ def valid_json?(json)
81
+ begin
82
+ JSON.parse(json)
83
+ return true
84
+ rescue JSON::ParserError => e
85
+ return false
86
+ end
87
+ end
88
+
89
+ def auth_params(client_id, client_secret)
90
+ {
91
+ 'client_id' => client_id,
92
+ 'client_secret' => client_secret,
93
+ 'grant_type' => DEFAULT_GRANT_TYPE
94
+ }
95
+ end
96
+
97
+ def environment_variables?
98
+ !ENV['MATROID_CLIENT_ID'].nil? && !ENV['MATROID_CLIENT_SECRET'].nil?
99
+ end
100
+
101
+ end
102
+
103
+ # Represents an OAuth access token
104
+ # @attr [String] token_type ex: "Bearer"
105
+ # @attr [String] token_str The actual access token
106
+ # @attr [DateTime] born When the token was created
107
+ # @attr [String] lifetime Seconds until token expired
108
+ class Token
109
+ attr_reader :born, :lifetime, :acces_token
110
+ def initialize(options = {})
111
+ @token_type = options['token_type']
112
+ @access_token = options['access_token']
113
+ @born = DateTime.now
114
+ @lifetime = options['expires_in']
115
+ end
116
+
117
+ def authorization_header
118
+ "#{@token_type} #{@access_token}"
119
+ end
120
+
121
+ # Checks if the current token is expired
122
+ # @return [Boolean]
123
+ def expired?
124
+ lifetime_in_days = time_in_seconds(@lifetime)
125
+ @born + lifetime_in_days < DateTime.now
126
+ end
127
+
128
+
129
+ # @return [Numeric] Time left before token expires (in seconds).
130
+ def time_remaining
131
+ lifetime_in_days = time_in_seconds(@lifetime)
132
+ remaining = lifetime_in_days - (DateTime.now - @born)
133
+ remaining > 0 ? time_in_seconds(remaining) : 0
134
+ end
135
+
136
+ def time_in_seconds(t)
137
+ t * 24.0 * 60 * 60
138
+ end
139
+
140
+ def to_s
141
+ JSON.pretty_generate({
142
+ access_token: @access_token,
143
+ born: @born,
144
+ lifetime: @lifetime
145
+ })
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,293 @@
1
+ # size limit constants
2
+ IMAGE_FILE_SIZE_LIMIT = 50 * 1024 * 1024
3
+ VIDEO_FILE_SIZE_LIMIT = 300 * 1024 * 1024
4
+ BATCH_FILE_SIZE_LIMIT = 50 * 1024 * 1024
5
+ ZIP_FILE_SIZE_LIMIT = 300 * 1024 * 1024
6
+ SEARCH_PARAMETERS = %w(name label permission_level owner training type state)
7
+ module Matroid
8
+
9
+
10
+ # Represents a Matroid detector
11
+ # @attr [String] id Detector id
12
+ # @attr [String] name Detector name
13
+ # @attr [Array<Hash><String>] labels
14
+ # @attr [String] permission_level 'private', 'readonly', 'open', 'stock'
15
+ # @attr [Bool] owner is the current authicated user the owner
16
+ class Detector
17
+ # HASH { <id> => Detector }
18
+ @@instances = {}
19
+ @@ids = []
20
+
21
+ attr_reader :id, :name, :labels, :label_ids, :permission_level, :owner, :training,
22
+ :type, :state
23
+
24
+ # Looks up the detector by matching fields.
25
+ # :label and :name get matched according the the regular expression /\b(<word>)/i
26
+ # @example
27
+ # Matroid::Detector.find(label: 'cat', state: 'trained')
28
+ # @example
29
+ # Matroid::Detector.find(name: 'cat')
30
+ # @example
31
+ # Matroid::Detector.find(name: 'cat', published: true)
32
+ # @example
33
+ # Matroid::Detector.find(label: 'puppy').first.id
34
+ # @note The detector must either be created by the current authenticated user or published for general use.
35
+ # @return [Array<Hash><Detector>] Returns the detector instances that match the query.
36
+ def self.find(args)
37
+ raise Error::InvalidQueryError.new('Argument must be a hash.') unless args.class == Hash
38
+ query = args.keys.map{|key| key.to_s + '=' + args[key].to_s }.join('&')
39
+ detectors = Matroid.get('detectors/search?' + query)
40
+ detectors.map{|params| register(params) }
41
+ end
42
+
43
+ # Chooses first occurence of the results from {#find}
44
+ def self.find_one(args)
45
+ args['limit'] = 1
46
+ find(args).first
47
+ end
48
+
49
+ # Finds a single document based on the id
50
+ def self.find_by_id(id, args = {})
51
+ detector = @@instances[id]
52
+ is_trained = detector.class == Detector && detector.is_trained?
53
+ return detector if is_trained
54
+
55
+ args[:id] = id
56
+ find_one(args)
57
+ end
58
+
59
+ SEARCH_PARAMETERS.each do |param|
60
+ # Search for detectors using the "find_one_by_" method prefix
61
+ define_singleton_method "find_one_by_#{param}" do |arg, opts = {}|
62
+ opts[param.to_sym] = arg
63
+ find_one(opts)
64
+ end
65
+
66
+ # Search for detectors using the "find_by_" method prefix
67
+ define_singleton_method "find_by_#{param}" do |arg, opts = {}|
68
+ opts[param.to_sym] = arg
69
+ find(opts)
70
+ end
71
+ end
72
+
73
+ # List of cached detectors that have been returned by search requests as hashes or Detector instances.
74
+ # @param type [String] Indicate how you want the response
75
+ # @return [Array<Hash, Detector>]
76
+ def self.cached(type = 'instance')
77
+ case type
78
+ when 'hash'
79
+ @@ids.map{|id| find_by_id(id).to_hash }
80
+ when 'instance'
81
+ @@ids.map{|id| find_by_id(id) }
82
+ end
83
+ end
84
+
85
+ # Removes all cached detector instances
86
+ def self.reset
87
+ @@instances = {}
88
+ @@ids = []
89
+ end
90
+
91
+ # Creates a new detector with the contents of a zip file.
92
+ # The root folder should contain only directories which will become the labels for detection.
93
+ # Each of these directories should contain only a images corresponding to that label.
94
+ # Zip file structure example:
95
+ # cat/
96
+ # garfield.jpg
97
+ # nermal.png
98
+ # dog/
99
+ # odie.tiff
100
+ # @note Max 1 GB zip file upload.
101
+ # @param zip_file [String] Path to zip file containing the images to be used in the detector creation
102
+ # @param name [String] The detector's display name
103
+ # @param detector_type [String] Options: "general", "face_detector", or "facial_characteristics"
104
+ # @return [Detector]
105
+ def self.create(zip_file, name, detector_type='general')
106
+ case zip_file
107
+ when String
108
+ file = File.new(zip_file, 'rb')
109
+ when File
110
+ file = zip_file
111
+ else
112
+ err_msg = 'First argument must be a zip file of the image folders, or a string of the path to the file'
113
+ raise Error::InvalidQueryError.new(err_msg)
114
+ end
115
+ params = {
116
+ file: file,
117
+ name: name,
118
+ detector_type: detector_type
119
+ }
120
+ response = Matroid.post('detectors', params)
121
+ id = response['detector_id']
122
+ find_by_id(id)
123
+ end
124
+
125
+ def initialize(params)
126
+ update_params(params)
127
+ end
128
+
129
+ # Detector attributes in a nicely printed format for viewing
130
+ def info
131
+ puts JSON.pretty_generate(to_hash)
132
+ end
133
+
134
+ # Detector attributes as a hash
135
+ # @return [Hash]
136
+ def to_hash
137
+ instance_variables.each_with_object(Hash.new(0)) do |element, hash|
138
+ hash["#{element}".delete("@").to_sym] = instance_variable_get(element)
139
+ end
140
+ end
141
+
142
+ # Submits detector instance for training
143
+ # @note
144
+ # Fails if detector is not qualified to be trained.
145
+ def train
146
+ raise Error::APIError.new("This detector is already trained.") if is_trained?
147
+ response = Matroid.post("detectors/#{@id}/finalize")
148
+ response['detector']
149
+ end
150
+
151
+ # @return [Boolean]
152
+ def is_trained?
153
+ @state == 'trained'
154
+ end
155
+
156
+ # Updates the the detector data. Used when training to see the detector training progress.
157
+ # @return [Detector]
158
+ def update
159
+ self.class.find_by_id(@id)
160
+ end
161
+
162
+ # Submits an image file via url to be classified with the detector
163
+ # @param url [String] Url for image file
164
+ # @return Hash containing the classification data.
165
+ # @example
166
+ # det = Matroid::Detector.find_by_id "5893f98530c1c00d0063835b"
167
+ # det.classify_image_url "https://www.allaboutbirds.org/guide/PHOTO/LARGE/common_tern_donnalynn.jpg"
168
+ # ### returns hash of results ###
169
+ # # {
170
+ # # "results": [
171
+ # # {
172
+ # # "file": {
173
+ # # "name": "image1.png",
174
+ # # "url": "https://myimages.1.png",
175
+ # # "thumbUrl": "https://myimages.1_t.png",
176
+ # # "filetype": "image/png"
177
+ # # },
178
+ # # "predictions": [
179
+ # # {
180
+ # # "bbox": {
181
+ # # "left": 0.7533333333333333,
182
+ # # "top": 0.4504347826086956,
183
+ # # "height": 0.21565217391304348,
184
+ # # "aspectRatio": 1.0434782608695652
185
+ # # },
186
+ # # "labels": {
187
+ # # "cat face": 0.7078468322753906,
188
+ # # "dog face": 0.29215322732925415
189
+ # # }
190
+ # # },
191
+ # # {
192
+ # # "bbox": {
193
+ # # "left": 0.4533333333333333,
194
+ # # "top": 0.6417391304347826,
195
+ # # "width": 0.20833333333333334,
196
+ # # "height": 0.21739130434782608,
197
+ # # "aspectRatio": 1.0434782608695652
198
+ # # },
199
+ # # "labels": {
200
+ # # "cat face": 0.75759859402753906,
201
+ # # "dog face": 0.45895322732925415
202
+ # # }
203
+ # # }, {
204
+ # # ...
205
+ # # }
206
+ # # ]
207
+ # # }
208
+ # # ]
209
+ # # }
210
+ def classify_image_url(url)
211
+ classify('image', url: url)
212
+ end
213
+
214
+ # Submits an image file via url to be classified with the detector
215
+ # @param url [String] Url for image file
216
+ # @return Hash containing the classification data see {#classify_image_url }
217
+ # @example
218
+ # det = Matroid::Detector.find_by_id "5893f98530c1c00d0063835b"
219
+ # det.classify_image_file "path/to/file.jpg"
220
+ def classify_image_file(file_path)
221
+ size_err = "Individual file size must be under #{IMAGE_FILE_SIZE_LIMIT / 1024 / 1024}MB"
222
+ raise Error::InvalidQueryError.new(size_err) if File.size(file_path) > IMAGE_FILE_SIZE_LIMIT
223
+ classify('image', file: File.new(file_path, 'rb'))
224
+ end
225
+
226
+ # The plural of {#classify_image_file}
227
+ # @param file_paths [Array<String>] An array of images in the form of paths from the current directory
228
+ # @return Hash containing the classification data
229
+ def classify_image_files(file_paths)
230
+ arg_err = "Error: Argument must be an array of image file paths"
231
+ size_err = "Error: Total batch size must be under #{BATCH_FILE_SIZE_LIMIT / 1024 / 1024}MB"
232
+ raise arg_err unless file_paths.is_a?(Array)
233
+ batch_size = file_paths.inject(0){ |sum, file| sum + File.size(file) }
234
+ raise size_err unless batch_size < BATCH_FILE_SIZE_LIMIT
235
+ files = file_paths.map{ |file_path| ['file', File.new(file_path, 'rb')] }
236
+
237
+ url = "#{Matroid.base_api_uri}detectors/#{@id}/classify_image"
238
+
239
+ client = HTTPClient.new
240
+ response = client.post(url, body: files, header: {'Authorization' => Matroid.token.authorization_header})
241
+ Matroid.parse_response(response)
242
+ # Matroid.post("detectors/#{@id}/classify_image", files) # responds with 'request entity too large' for some reason
243
+ end
244
+
245
+ # Submits a video file via url to be classified with the detector
246
+ # @param url [String] Url for video file
247
+ # @return Hash containing the registered video's id; ex: { "video_id" => "58489472ff22bb2d3f95728c" }. Needed for Matroid.get_video_results(video_id)
248
+ def classify_video_url(url)
249
+ classify('video', url: url)
250
+ end
251
+
252
+ # Submits a local video file to be classified with the detector
253
+ # @param file_path [String] Path to file
254
+ # @return Hash containing the registered video's id ex: { "video_id" => "58489472ff22bb2d3f95728c" }. Needed for Matroid.get_video_results(video_id)
255
+ def classify_video_file(file_path)
256
+ size_err = "Video file size must be under #{VIDEO_FILE_SIZE_LIMIT / 1024 / 1024}MB"
257
+ raise Error::InvalidQueryError.new(size_err) if File.size(file_path) > VIDEO_FILE_SIZE_LIMIT
258
+ classify('video', file: File.new(file_path, 'rb'))
259
+ end
260
+
261
+ def update_params(params)
262
+ @id = params['id'] if params['id']
263
+ @name = params['name'] if params['name']
264
+ @labels = params['labels'] if params['labels']
265
+ @label_ids = params['label_ids'] if params['label_ids']
266
+ @permission_level = params['permission_level'] if params['permission_level']
267
+ @owner = params['owner'] if params['owner']
268
+ @type = params['type'] if params['type']
269
+ @training = params['training'] if params['training']
270
+ @state = params['state'] if params['state']
271
+ self
272
+ end
273
+
274
+ private
275
+
276
+ def classify(type, params)
277
+ not_trained_err = "This detector's training is not complete."
278
+ raise Error::InvalidQueryError.new(not_trained_err) unless is_trained?
279
+ Matroid.post("detectors/#{@id}/classify_#{type}", params)
280
+ end
281
+
282
+ def self.register(obj)
283
+ id = obj['id']
284
+ @@ids.push(id) if @@instances[id].nil?
285
+ if @@instances[id].class == Detector
286
+ @@instances[id].update_params(obj)
287
+ else
288
+ @@instances[id] = Detector.new(obj)
289
+ end
290
+ end
291
+
292
+ end
293
+ end
@@ -0,0 +1,12 @@
1
+ module Matroid
2
+ module Error
3
+ class APIError < StandardError; end
4
+ class APIConnectionError < APIError; end
5
+ class AuthorizationError < APIError; end
6
+ class InvalidQueryError < APIError; end
7
+ class ServerError < APIError; end
8
+ class RateLimitError < APIError; end
9
+ class PaymentError < APIError; end
10
+ class MediaError < APIError; end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Matroid
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/matroid.rb CHANGED
@@ -1,43 +1,82 @@
1
- require "matroid/version"
2
- require 'httparty'
3
- require 'pry'
1
+ require 'dotenv/load'
2
+ require 'matroid/connection'
3
+ require 'matroid/version'
4
+ require 'matroid/detector'
5
+ require 'matroid/error'
4
6
 
5
7
  module Matroid
6
- class MatroidClient
7
- def initialize(options)
8
- @base_url = options[:base_url]
9
- @client_id = options[:client_id]
10
- @client_secret = options[:client_secret]
11
- @request = HTTParty
12
-
13
- @endpoints = {
14
- token: { method: :post, uri: "#{@base_url}/oauth/token" },
15
- account_info: { method: :get, uri: "#{@base_url}/account" }
16
- }
8
+ @client_id = ENV['MATROID_CLIENT_ID']
9
+ @client_secret = ENV['MATROID_CLIENT_SECRET']
10
+
11
+ class << self
12
+
13
+ # Authenticates access for Matroid API
14
+ # @example
15
+ # Matroid.authenticate("<your_client_id>", "<your_client_secret>")
16
+ # @param client_id [String]
17
+ # @param client_secret [String]
18
+ # @return [Boolean] If the the access token is successfully created.
19
+ def authenticate(client_id = nil, client_secret = nil)
20
+ return true unless @token.nil? || @token.expired?
21
+ if client_id && client_secret
22
+ err_msg = 'problem using environment variables "MATROID_CLIENT_ID" and "MATROID_CLIENT_SECRET"'
23
+ new_token = get_token(client_id, client_secret)
24
+ raise Error::AuthorizationError.new(err_msg) if new_token.nil?
25
+ @client_id, @client_secret = client_id, client_secret
26
+ elsif (@client_id.nil? || @client_secret.nil?) && !environment_variables?
27
+ err_msg = '"MATROID_CLIENT_ID" and "MATROID_CLIENT_SECRET" not found in environment'
28
+ raise Error::AuthorizationError.new(err_msg)
29
+ else
30
+ err_msg = 'Problem using client variables provided'
31
+ raise Error::AuthorizationError.new(err_msg) if get_token(@client_id, @client_secret).nil?
32
+ end
33
+
34
+ true
17
35
  end
18
36
 
19
- def retrieve_token
20
- endpoint = @endpoints[:token]
21
- opts = {
22
- body: {
23
- client_id: @client_id,
24
- client_secret: @client_secret,
25
- grant_type: 'client_credentials'
26
- }
27
- }
28
- response = @request.send(endpoint[:method], endpoint[:uri], opts)
29
- @authorization_header = "#{response['token_type']} #{response['access_token']}"
30
- response.parsed_response
37
+ # Calls ::show on the current token (if it exists).
38
+ def show_token
39
+ if @token
40
+ @token.show
41
+ end
31
42
  end
32
43
 
44
+ # Retrieves the authenticated user's account information
45
+ # @return The account info as a parsed JSON
46
+ # @example
47
+ # {
48
+ # "account" => {
49
+ # "credits" => {
50
+ # "concurrentTrainLimit" =>1,
51
+ # "held" => 3496,
52
+ # "plan" => "premium",
53
+ # "daily" => {
54
+ # "used" => 36842,
55
+ # "available" => 100000
56
+ # },
57
+ # "monthly" => {
58
+ # "used" => 36842,
59
+ # "available" => 1000000
60
+ # }
61
+ # }
62
+ # }
63
+ # }
33
64
  def account_info
34
- endpoint = @endpoints[:account_info]
35
- headers = { "Authorization" => @authorization_header }
36
- opts = {
37
- headers: headers
38
- }
39
- response = @request.send(endpoint[:method], endpoint[:uri], opts)
40
- response.parsed_response
65
+ get('account')
41
66
  end
67
+
68
+ # Retrieves video classification data. Requires a video_id from
69
+ # {Detector#classify_video_file} or {Detector#classify_video_url}
70
+ # format 'json'/'csv'
71
+ # @note A "video_id" is needed to get the classification results
72
+ # @example
73
+ # <Detector >.get_video_results(video_id: "23498503uf0dd09", threshold: 30, format: 'json')
74
+ # @param video_id [String]
75
+ # @param threshold [Numeric, nil]
76
+ # @param
77
+ def get_video_results(video_id, *args)
78
+ get("videos/#{video_id}", *args)
79
+ end
80
+
42
81
  end
43
82
  end
data/matroid.gemspec CHANGED
@@ -30,7 +30,18 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
+ spec.add_dependency 'omniauth-oauth2', '~> 1.3.1'
34
+ spec.add_dependency 'httpclient'
35
+
33
36
  spec.add_development_dependency "bundler", "~> 1.14"
34
37
  spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_runtime_dependency 'httparty', "~> 0.14.0"
38
+ spec.add_development_dependency "minitest"
39
+ spec.add_development_dependency "vcr"
40
+ spec.add_development_dependency "fakeweb"
41
+ spec.add_development_dependency "webmock"
42
+ spec.add_development_dependency "rspec"
43
+
44
+ spec.add_dependency "dotenv"
45
+ spec.add_dependency "faraday"
46
+ spec.add_dependency "json"
36
47
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matroid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matroid
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-17 00:00:00.000000000 Z
11
+ date: 2017-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: omniauth-oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.3.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: httpclient
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +67,117 @@ dependencies:
39
67
  - !ruby/object:Gem::Version
40
68
  version: '10.0'
41
69
  - !ruby/object:Gem::Dependency
42
- name: httparty
70
+ name: minitest
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
- - - "~>"
73
+ - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: 0.14.0
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fakeweb
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: dotenv
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
48
146
  type: :runtime
49
147
  prerelease: false
50
148
  version_requirements: !ruby/object:Gem::Requirement
51
149
  requirements:
52
- - - "~>"
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: faraday
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: json
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
53
179
  - !ruby/object:Gem::Version
54
- version: 0.14.0
180
+ version: '0'
55
181
  description: Check your account status on Matroid. More features coming soon.
56
182
  email:
57
183
  - solutions@matroid.com
@@ -67,6 +193,9 @@ files:
67
193
  - bin/console
68
194
  - bin/setup
69
195
  - lib/matroid.rb
196
+ - lib/matroid/connection.rb
197
+ - lib/matroid/detector.rb
198
+ - lib/matroid/error.rb
70
199
  - lib/matroid/version.rb
71
200
  - matroid.gemspec
72
201
  homepage: http://www.matroid.com
@@ -90,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
219
  version: '0'
91
220
  requirements: []
92
221
  rubyforge_project:
93
- rubygems_version: 2.5.1
222
+ rubygems_version: 2.6.8
94
223
  signing_key:
95
224
  specification_version: 4
96
225
  summary: Matroid API Ruby Library