algorithmia 0.2.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/algorithmia.rb CHANGED
@@ -1,18 +1,31 @@
1
- # Algorithmia Ruby wrapper.
2
- # Released under the MIT License
3
- # http://github.com/bih/algorithmia
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
- $:.push File.expand_path("../lib", __FILE__)
12
+ module Algorithmia
6
13
 
7
- require 'algorithmia/base'
8
- require 'algorithmia/version'
9
- require 'algorithmia/errors'
10
- require 'algorithmia/authentication'
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
- class Algorithmia
15
- def self.call(endpoint, input)
16
- post_http("/#{endpoint}", input.to_json)
17
- end
18
- end
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
@@ -1,6 +1,20 @@
1
- # Algorithmia Ruby wrapper.
2
- # Released under the MIT License
3
- # http://github.com/bih/algorithmia
1
+ module Algorithmia
2
+ module Errors
3
+ class Error < StandardError
4
+ attr_reader :response
4
5
 
5
- class AlgorithmiaException < Exception; end
6
- class AlgorithmiaApiKeyTooShort < Exception; end
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