algorithmia 0.2.0 → 0.9.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.
- 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
|