algorithmia 0.2.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +12 -1
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +3 -3
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/Rakefile +6 -63
- data/algorithmia.gemspec +22 -24
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/algorithmia.rb +28 -15
- data/lib/algorithmia/algorithm.rb +49 -0
- data/lib/algorithmia/client.rb +21 -0
- data/lib/algorithmia/data_directory.rb +91 -0
- data/lib/algorithmia/data_file.rb +55 -0
- data/lib/algorithmia/data_object.rb +26 -0
- data/lib/algorithmia/errors.rb +19 -5
- data/lib/algorithmia/requester.rb +104 -0
- data/lib/algorithmia/response.rb +39 -0
- data/lib/algorithmia/unauthenticated_client.rb +7 -0
- data/lib/algorithmia/version.rb +3 -7
- metadata +56 -36
- data/.ruby-version +0 -1
- data/LICENSE.txt +0 -8
- data/algorithmia.png +0 -0
- data/lib/algorithmia/authentication.rb +0 -24
- data/lib/algorithmia/base.rb +0 -14
- data/lib/algorithmia/http.rb +0 -33
- data/lib/algorithmia/result.rb +0 -25
- data/readme.markdown +0 -37
- data/spec/algorithmia_spec.rb +0 -17
data/bin/setup
ADDED
data/lib/algorithmia.rb
CHANGED
@@ -1,18 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'algorithmia/algorithm'
|
2
|
+
require_relative 'algorithmia/client'
|
3
|
+
require_relative 'algorithmia/unauthenticated_client'
|
4
|
+
require_relative 'algorithmia/errors'
|
5
|
+
require_relative 'algorithmia/requester'
|
6
|
+
require_relative 'algorithmia/response'
|
7
|
+
require_relative 'algorithmia/version'
|
8
|
+
require_relative 'algorithmia/data_object'
|
9
|
+
require_relative 'algorithmia/data_file'
|
10
|
+
require_relative 'algorithmia/data_directory'
|
4
11
|
|
5
|
-
|
12
|
+
module Algorithmia
|
6
13
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require 'algorithmia/result'
|
12
|
-
require 'algorithmia/http'
|
14
|
+
class << self
|
15
|
+
def algo(endpoint)
|
16
|
+
Algorithmia::UnauthenticatedClient.new.algo(endpoint)
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
def file(data_uri)
|
20
|
+
Algorithmia::UnauthenticatedClient.new.file(data_uri)
|
21
|
+
end
|
22
|
+
|
23
|
+
def dir(data_uri)
|
24
|
+
Algorithmia::UnauthenticatedClient.new.dir(data_uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
def client(api_key)
|
28
|
+
Algorithmia::Client.new(api_key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Algorithmia
|
2
|
+
class Algorithm
|
3
|
+
|
4
|
+
def initialize(client, endpoint)
|
5
|
+
@client = client
|
6
|
+
@endpoint = '/v1/algo/' + endpoint
|
7
|
+
@query = {
|
8
|
+
timeout: 300,
|
9
|
+
stdout: false,
|
10
|
+
output: 'default'
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_options(options_hash)
|
15
|
+
@query_options.update(options_hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_timeout(timeout)
|
19
|
+
@query_options[:timeout] = timeout.to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
def enable_stdout
|
23
|
+
@query_options[:stdout] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def pipe(input)
|
27
|
+
content_type = case
|
28
|
+
when input.kind_of?(String) && input.encoding == Encoding::ASCII_8BIT
|
29
|
+
'application/octet-stream'
|
30
|
+
when input.kind_of?(String)
|
31
|
+
'text/plain'
|
32
|
+
else
|
33
|
+
'application/json'
|
34
|
+
end
|
35
|
+
|
36
|
+
headers = {
|
37
|
+
'Content-Type' => content_type
|
38
|
+
}
|
39
|
+
|
40
|
+
response = Algorithmia::Requester.new(@client).post(@endpoint, input, query: @query, headers: headers)
|
41
|
+
Algorithmia::Response.new(response.parsed_response)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pipe_json(input)
|
45
|
+
response = Algorithmia::Requester.new(@client).post(@endpoint, input, query: @query, headers: {})
|
46
|
+
Algorithmia::Response.new(response.parsed_response)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Algorithmia
|
2
|
+
class Client
|
3
|
+
attr_reader :api_key
|
4
|
+
|
5
|
+
def initialize(api_key)
|
6
|
+
@api_key = api_key
|
7
|
+
end
|
8
|
+
|
9
|
+
def algo(endpoint)
|
10
|
+
Algorithmia::Algorithm.new(self, endpoint)
|
11
|
+
end
|
12
|
+
|
13
|
+
def file(endpoint)
|
14
|
+
Algorithmia::DataFile.new(self, endpoint)
|
15
|
+
end
|
16
|
+
|
17
|
+
def dir(endpoint)
|
18
|
+
Algorithmia::DataDirectory.new(self, endpoint)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Algorithmia
|
4
|
+
class DataDirectory < DataObject
|
5
|
+
|
6
|
+
def exists?
|
7
|
+
Algorithmia::Requester.new(@client).get(@url)
|
8
|
+
true
|
9
|
+
rescue Errors::NotFoundError
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
parent, name = File.split(@url)
|
15
|
+
Algorithmia::Requester.new(@client).post(parent, name: name)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(force = false)
|
20
|
+
query = {}
|
21
|
+
query[:force] = true if force
|
22
|
+
Algorithmia::Requester.new(@client).delete(@url, query: query)
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
return enum_for(:each) unless block_given?
|
28
|
+
|
29
|
+
list(block) do |dir|
|
30
|
+
extract_files(dir) + extract_folders(dir)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def each_file(&block)
|
35
|
+
return enum_for(:each_file) unless block_given?
|
36
|
+
|
37
|
+
list(block) do |dir|
|
38
|
+
extract_files(dir)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def each_dir(&block)
|
43
|
+
return enum_for(:each_dir) unless block_given?
|
44
|
+
|
45
|
+
list(block) do |dir|
|
46
|
+
extract_folders(dir)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def file(file_name)
|
51
|
+
@client.file(URI.encode(File.join(@data_uri, file_name)))
|
52
|
+
end
|
53
|
+
|
54
|
+
def put_file(file_path)
|
55
|
+
file(File.basename(file_path)).put_file(file_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def extract_files(dir)
|
61
|
+
files = dir.parsed_response['files'] || []
|
62
|
+
files.map do |f|
|
63
|
+
file(f['filename'])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def extract_folders(dir)
|
68
|
+
folders = dir.parsed_response['folders'] || []
|
69
|
+
folders.map do |f|
|
70
|
+
@client.dir(URI.encode(File.join(@data_uri, f['name'])))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def list(each_proc)
|
75
|
+
marker = nil
|
76
|
+
|
77
|
+
loop do
|
78
|
+
query = {}
|
79
|
+
query[:marker] = marker if marker
|
80
|
+
|
81
|
+
dir = Algorithmia::Requester.new(@client).get(@url, query: query)
|
82
|
+
|
83
|
+
items = yield dir
|
84
|
+
items.each(&each_proc)
|
85
|
+
|
86
|
+
marker = dir.parsed_response['marker']
|
87
|
+
break unless marker && !marker.empty?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Algorithmia
|
4
|
+
class DataFile < DataObject
|
5
|
+
|
6
|
+
def exists?
|
7
|
+
Algorithmia::Requester.new(@client).head(@url)
|
8
|
+
true
|
9
|
+
rescue Errors::NotFoundError
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_file
|
14
|
+
response = get
|
15
|
+
|
16
|
+
tempfile = Tempfile.open(File.basename(@url)) do |f|
|
17
|
+
f.write response
|
18
|
+
f
|
19
|
+
end
|
20
|
+
|
21
|
+
File.new(tempfile.path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get
|
25
|
+
Algorithmia::Requester.new(@client).get(@url).body
|
26
|
+
end
|
27
|
+
|
28
|
+
def put(data)
|
29
|
+
content_type = case
|
30
|
+
when data.kind_of?(String) && data.encoding == Encoding::ASCII_8BIT
|
31
|
+
'application/octet-stream'
|
32
|
+
when data.kind_of?(String)
|
33
|
+
'text/plain'
|
34
|
+
else
|
35
|
+
'application/json'
|
36
|
+
end
|
37
|
+
|
38
|
+
headers = {'Content-Type' => content_type }
|
39
|
+
Algorithmia::Requester.new(@client).put(@url, data, headers: headers)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :put_json, :put
|
44
|
+
|
45
|
+
def put_file(file_path)
|
46
|
+
data = File.binread(file_path)
|
47
|
+
put(data)
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete
|
51
|
+
Algorithmia::Requester.new(@client).delete(@url)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Algorithmia
|
2
|
+
class DataObject
|
3
|
+
attr_reader :data_uri
|
4
|
+
|
5
|
+
def initialize(client, data_uri)
|
6
|
+
@client = client
|
7
|
+
@data_uri = data_uri
|
8
|
+
sanitize_data_uri
|
9
|
+
end
|
10
|
+
|
11
|
+
def basename
|
12
|
+
File.basename(@url)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parent
|
16
|
+
@client.dir(File.split(@data_uri).first)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def sanitize_data_uri
|
22
|
+
file_path = @data_uri.gsub('data://', '')
|
23
|
+
@url = File.join('/v1/data/', file_path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/algorithmia/errors.rb
CHANGED
@@ -1,6 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Algorithmia
|
2
|
+
module Errors
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :response
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
def initialize(message, response)
|
7
|
+
super(message)
|
8
|
+
@response = response
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ApiKeyEmptyError < Error; end
|
13
|
+
class ApiKeyInvalidError < Error; end
|
14
|
+
class InternalServerError < Error; end
|
15
|
+
class JsonParseError < Error; end
|
16
|
+
class NotFoundError < Error; end
|
17
|
+
class UnauthorizedError < Error; end
|
18
|
+
class UnknownError < Error; end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Algorithmia
|
4
|
+
class Requester
|
5
|
+
include HTTParty
|
6
|
+
base_uri "https://api.algorithmia.com"
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
@default_headers = {
|
11
|
+
'Authorization' => @client.api_key || '',
|
12
|
+
'Content-Type' => 'application/json',
|
13
|
+
'User-Agent' => 'Algorithmia Ruby Client'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(endpoint, query: {}, headers: {})
|
18
|
+
headers = merge_headers(headers)
|
19
|
+
response = self.class.get(endpoint, query: query, headers: headers)
|
20
|
+
check_for_errors(response)
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
def post(endpoint, body, query: {}, headers: {})
|
25
|
+
headers = merge_headers(headers)
|
26
|
+
|
27
|
+
if headers['Content-Type'] == 'application/json'
|
28
|
+
body = body.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
response = self.class.post(endpoint, body: body, query: query, headers: headers)
|
32
|
+
check_for_errors(response)
|
33
|
+
response
|
34
|
+
end
|
35
|
+
|
36
|
+
def put(endpoint, body, query: {}, headers: {})
|
37
|
+
headers = merge_headers(headers)
|
38
|
+
|
39
|
+
if headers['Content-Type'] == 'application/json'
|
40
|
+
body = body.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
response = self.class.put(endpoint, body: body, query: query, headers: headers)
|
44
|
+
check_for_errors(response)
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
def head(endpoint)
|
49
|
+
response = self.class.head(endpoint, headers: @default_headers)
|
50
|
+
check_for_errors(response)
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete(endpoint, query: {})
|
55
|
+
response = self.class.delete(endpoint, query: query, headers: @default_headers)
|
56
|
+
check_for_errors(response)
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def check_for_errors(response)
|
63
|
+
if response.code >= 200 && response.code < 300
|
64
|
+
parse_error_message(response) if response['error']
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
case response.code
|
70
|
+
when 401
|
71
|
+
raise Errors::UnauthorizedError.new("The request you are making requires authorization. Please check that you have permissions & that you've set your API key.", response)
|
72
|
+
when 400
|
73
|
+
parse_error_message(response)
|
74
|
+
when 404
|
75
|
+
raise Errors::NotFoundError.new("The URI requested is invalid or the resource requested does not exist.", response)
|
76
|
+
when 500
|
77
|
+
raise Errors::InternalServerError.new("Whoops! Something is broken.", response)
|
78
|
+
else
|
79
|
+
raise Errors::UnknownError.new("The error you encountered returned the message: #{response["error"]["message"]} with stacktrace: #{error["stacktrace"]}", response)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_error_message(response)
|
84
|
+
error = response['error']
|
85
|
+
|
86
|
+
case error["message"]
|
87
|
+
when 'authorization required'
|
88
|
+
raise Errors::ApiKeyInvalidError.new("The API key you sent is invalid! Please set `Algorithmia::Client.api_key` with the key provided with your account.", response)
|
89
|
+
when 'Failed to parse input, input did not parse as valid json'
|
90
|
+
raise Errors::JsonParseError.new("Unable to parse the input. Please make sure it matches the expected input of the algorithm and can be parsed into JSON.", response)
|
91
|
+
else
|
92
|
+
if error["stacktrace"].nil?
|
93
|
+
raise Errors::UnknownError.new("The error you encountered returned the message: #{error["message"]}", response)
|
94
|
+
else
|
95
|
+
raise Errors::UnknownError.new("The error you encountered returned the message: #{error["message"]} with stacktrace: #{error["stacktrace"]}", response)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge_headers(headers = {})
|
101
|
+
@default_headers.merge(headers)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module Algorithmia
|
4
|
+
class Response
|
5
|
+
attr_reader :json
|
6
|
+
|
7
|
+
def initialize(result)
|
8
|
+
@json = result
|
9
|
+
end
|
10
|
+
|
11
|
+
def result
|
12
|
+
if content_type == 'binary'
|
13
|
+
Base64.decode64(@json["result"])
|
14
|
+
else
|
15
|
+
@json["result"]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def metadata
|
20
|
+
@json["metadata"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def duration
|
24
|
+
metadata["duration"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def content_type
|
28
|
+
metadata["content_type"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def stdout
|
32
|
+
metadata["stdout"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def alerts
|
36
|
+
metadata["alerts"]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|