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