matroid 0.0.1 → 0.0.3

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