datarobot-ai_api 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +3 -0
  4. data/README.md +12 -0
  5. data/Rakefile +54 -0
  6. data/datarobot-ai_api.gemspec +19 -0
  7. data/lib/datarobot.rb +1 -0
  8. data/lib/datarobot/ai_api.rb +190 -0
  9. data/lib/datarobot/ai_api/ai.rb +191 -0
  10. data/lib/datarobot/ai_api/dataset.rb +99 -0
  11. data/lib/datarobot/ai_api/deployment.rb +20 -0
  12. data/lib/datarobot/ai_api/evaluation.rb +21 -0
  13. data/lib/datarobot/ai_api/learning_session.rb +112 -0
  14. data/lib/datarobot/ai_api/output.rb +58 -0
  15. data/lib/datarobot/ai_api/page.rb +50 -0
  16. data/lib/datarobot/ai_api/prediction.rb +20 -0
  17. data/lib/datarobot/ai_api/refreshable.rb +20 -0
  18. data/lib/datarobot/ai_api/task.rb +86 -0
  19. data/test/cassettes/Datarobot_AiApi/_ping/returns_unauthorized_when_not_authorized.yml +41 -0
  20. data/test/cassettes/Datarobot_AiApi/_ping/works_when_authorized.yml +58 -0
  21. data/test/cassettes/Datarobot_AiApi/_ping_me/returns_unauthorized_when_not_authorized.yml +41 -0
  22. data/test/cassettes/Datarobot_AiApi/_ping_me/works_when_authorized.yml +58 -0
  23. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/404s_if_there_is_no_matching_output.yml +190 -0
  24. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_find/should_accept_a_block.yml +66 -0
  25. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_next_page/can_go_to_the_next_page.yml +132 -0
  26. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_next_page/returns_the_same_page_if_there_is_no_next_page.yml +123 -0
  27. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_previous_page/can_go_to_the_previous_page.yml +262 -0
  28. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_previous_page/returns_the_same_page_if_there_is_no_previous_page.yml +67 -0
  29. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_refresh/can_refresh_to_a_new_object.yml +129 -0
  30. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/_refresh_/can_refresh_its_values.yml +129 -0
  31. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_add_datasets_to_AIs.yml +120 -0
  32. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_add_learning_sessions_to_AIs.yml +120 -0
  33. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_be_trained.yml +4554 -0
  34. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_create_AIs.yml +120 -0
  35. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_delete_AIs.yml +58 -0
  36. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_find_AIs.yml +63 -0
  37. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_get_AI_datasets.yml +131 -0
  38. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_get_AI_outputs.yml +124 -0
  39. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_list_AIs.yml +123 -0
  40. data/test/cassettes/Datarobot_AiApi_AI/when_authorized/can_predict_things.yml +190 -0
  41. data/test/cassettes/Datarobot_AiApi_AI/when_not_authorized/can_not_create_AIs.yml +43 -0
  42. data/test/cassettes/Datarobot_AiApi_AI/when_not_authorized/can_not_find_AIs.yml +43 -0
  43. data/test/cassettes/Datarobot_AiApi_AI/when_not_authorized/can_not_list_AIs.yml +45 -0
  44. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_find/should_accept_a_block.yml +64 -0
  45. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_next_page/can_go_to_the_next_page.yml +126 -0
  46. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_next_page/returns_the_same_page_if_there_is_no_next_page.yml +113 -0
  47. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_previous_page/can_go_to_the_previous_page.yml +250 -0
  48. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_previous_page/returns_the_same_page_if_there_is_no_previous_page.yml +64 -0
  49. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_refresh/can_refresh_to_a_new_object.yml +125 -0
  50. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/_refresh_/can_refresh_its_values.yml +125 -0
  51. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/can_delete_datasets.yml +58 -0
  52. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/can_find_datasets.yml +61 -0
  53. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/can_import_a_dataset_from_a_file.yml +1077 -0
  54. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/can_import_a_dataset_from_a_url.yml +55 -0
  55. data/test/cassettes/Datarobot_AiApi_Dataset/when_authorized/can_list_datasets.yml +113 -0
  56. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_find/should_accept_a_block.yml +67 -0
  57. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_next_page/can_go_to_the_next_page.yml +67 -0
  58. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_next_page/returns_the_same_page_if_there_is_no_next_page.yml +100 -0
  59. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_previous_page/can_go_to_the_previous_page.yml +67 -0
  60. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_previous_page/returns_the_same_page_if_there_is_no_previous_page.yml +100 -0
  61. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_refresh/can_refresh_to_a_new_object.yml +131 -0
  62. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/_refresh_/can_refresh_its_values.yml +130 -0
  63. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_create_learning_sessions.yml +2815 -0
  64. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_delete_learning_sessions.yml +58 -0
  65. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_find_learning_sessions.yml +63 -0
  66. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_get_learning_session_deployment_data.yml +123 -0
  67. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_get_learning_session_evaluation_data.yml +67 -0
  68. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_get_learning_session_features.yml +127 -0
  69. data/test/cassettes/Datarobot_AiApi_LearningSession/when_authorized/can_list_learning_sessions.yml +100 -0
  70. data/test/cassettes/Datarobot_AiApi_Output/when_authorized/can_create_outputs.yml +60 -0
  71. data/test/cassettes/Datarobot_AiApi_Output/when_authorized/can_find_named_outputs.yml +123 -0
  72. data/test/cassettes/Datarobot_AiApi_Output/when_authorized/can_get_features_of_an_output.yml +184 -0
  73. data/test/cassettes/Datarobot_AiApi_Output/when_authorized/can_get_the_evaluation_of_an_output.yml +131 -0
  74. data/test/cassettes/Datarobot_AiApi_Output/when_authorized/can_list_outputs.yml +124 -0
  75. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/_find/should_accept_a_block.yml +63 -0
  76. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/_refresh/can_refresh_to_a_new_object.yml +124 -0
  77. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/_refresh_/can_refresh_its_values.yml +125 -0
  78. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/can_stop_a_task.yml +58 -0
  79. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/can_wait_until_completed.yml +1138 -0
  80. data/test/cassettes/Datarobot_AiApi_Task/when_authorized/check_the_status_of_a_task.yml +64 -0
  81. data/test/data/delete-me.csv +8037 -0
  82. data/test/data/test.csv +8037 -0
  83. data/test/datarobot/ai_api/test_ai.rb +133 -0
  84. data/test/datarobot/ai_api/test_dataset.rb +64 -0
  85. data/test/datarobot/ai_api/test_learning_session.rb +77 -0
  86. data/test/datarobot/ai_api/test_output.rb +54 -0
  87. data/test/datarobot/ai_api/test_page.rb +50 -0
  88. data/test/datarobot/ai_api/test_prediction.rb +0 -0
  89. data/test/datarobot/ai_api/test_refreshable.rb +39 -0
  90. data/test/datarobot/ai_api/test_task.rb +53 -0
  91. data/test/datarobot/test_ai_api.rb +52 -0
  92. data/test/test_helper.rb +40 -0
  93. metadata +217 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7763e2dae82034b312b2daf53061aafa7a1db5f091964a118c5eca2ecc3f8b33
4
+ data.tar.gz: 1b0ad22bf75f5ef9742c5baedbbb9784ff56a3d533f7034f91b838e3d91c7b1c
5
+ SHA512:
6
+ metadata.gz: 742b703100a6ca1d24fd5c37c7e39a1f8ee94cc23d756136b78910c66ec182105cf5f5921e4bb6d6d48557098d310d05aebce6871073b93ad3eec18ebdf74578
7
+ data.tar.gz: 2fafe6e650f301e35c5ccc3207ee09e86c117b822e36bce90ebd47cde56a7f05d66221f4a6d80a7f8f6ae72111bbb96fcdb4b1cdfe028f7c7098452122f8fa71
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ doc
3
+ .rake_tasks~
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,12 @@
1
+ ## Objects
2
+
3
+ * AI
4
+ * Datasets
5
+ * Learning Sessions
6
+ * Outputs
7
+
8
+ ## Extra endpoints
9
+
10
+ * Ping me
11
+ * Ping
12
+ * Status
@@ -0,0 +1,54 @@
1
+ # coding:utf-8
2
+
3
+ require 'rubygems'
4
+ require 'rubygems/specification'
5
+ require 'rake'
6
+
7
+ def gemspec
8
+ @gemspec ||= begin
9
+ gem_name = "datarobot-ai_api"
10
+ file = File.expand_path("../#{gem_name}.gemspec", __FILE__)
11
+ eval(File.read(file), binding, file)
12
+ end
13
+ end
14
+
15
+ require 'rake/testtask'
16
+ Rake::TestTask.new(:test) do |test|
17
+ test.libs << 'test'
18
+ test.pattern = 'test/**/test_*.rb'
19
+ test.verbose = true
20
+ end
21
+
22
+ begin
23
+ require 'rake/gempackagetask'
24
+ rescue LoadError
25
+ task(:gem) { $stderr.puts '`gem install rake` to package gems' }
26
+ else
27
+ Rake::GemPackageTask.new(gemspec) do |pkg|
28
+ pkg.gem_spec = gemspec
29
+ end
30
+ task :gem => :gemspec
31
+ end
32
+
33
+ desc "Validates the gemspec"
34
+ task :gemspec do
35
+ gemspec.validate
36
+ end
37
+
38
+ desc "Displays the current version"
39
+ task :version do
40
+ puts "Current version: #{gemspec.version}"
41
+ end
42
+
43
+ desc "Installs the gem locally"
44
+ task :install => :package do
45
+ sh "gem install pkg/#{gemspec.name}-#{gemspec.version}"
46
+ end
47
+
48
+ desc "Release the gem"
49
+ task :release => :package do
50
+ sh "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
51
+ end
52
+
53
+ task :package => :gemspec
54
+ task :default => :test
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'datarobot-ai_api'
3
+ s.version = '0.0.3'
4
+ s.date = '2019-04-16'
5
+ s.summary = "A Ruby wrapper for the DataRobot AI API"
6
+ s.description = "Provides an interface to the AI API"
7
+ s.authors = ["AI API team"]
8
+ s.email = 'ai-api-team@datarobot.com'
9
+ s.files = %w(.gitignore README.md Rakefile datarobot-ai_api.gemspec lib/datarobot.rb Gemfile) + Dir.glob("{lib,test}/**/*")
10
+ s.homepage =
11
+ 'https://developers.datarobot.com/api'
12
+ s.license = 'Nonstandard'
13
+ s.add_development_dependency "minitest", "~> 5.8"
14
+ s.add_development_dependency "minitest-reporters", "~> 1.1"
15
+ s.add_development_dependency "minitest-vcr", "~> 1.4"
16
+ s.add_development_dependency "webmock", "~> 3.6"
17
+ s.add_development_dependency 'pry', '~> 0.12.2'
18
+ s.add_runtime_dependency 'multipart-post', '~> 2.1'
19
+ end
@@ -0,0 +1 @@
1
+ require_relative 'datarobot/ai_api'
@@ -0,0 +1,190 @@
1
+ require 'net/https'
2
+ require 'json'
3
+ require 'cgi'
4
+ require "net/http/post/multipart"
5
+
6
+ module Datarobot
7
+ module AiApi
8
+ class UnauthorizedError < StandardError; end;
9
+ class NotFoundError < StandardError; end;
10
+ class ApiError < StandardError; end;
11
+
12
+ @@api_key = ''
13
+ @@base_url = 'https://developers.datarobot.com'
14
+
15
+ def self.api_key; @@api_key end
16
+ def self.api_key= v; @@api_key = v end
17
+
18
+ # Sets global API key
19
+ # @param [String] api_key The api key to use for authentication
20
+ # @return [Datarobot::AiApi]
21
+ def self.configure!(api_key: '', base_url: nil)
22
+ @@api_key = api_key
23
+ @@base_url = base_url if base_url
24
+ self
25
+ end
26
+
27
+ # Checks the `ping` endpoint. Useful for debugging
28
+ # @return [Hash]
29
+ def self.ping
30
+ request_endpoint('/aiapi/ping')
31
+ end
32
+
33
+ # Checks the `ping/me` endpoint. Useful for debugging authentication
34
+ # @return [Hash]
35
+ def self.ping_me
36
+ request_endpoint('/aiapi/ping/me')
37
+ end
38
+
39
+ # Adds default headers to a net http request
40
+ # @param [Net::HTTP::*] request The net/http request to add the headers to
41
+ # @param [Hash] additional_headers Will overwrite defaults
42
+ # @return [Net::Http::*] returns an aribitrary request type (GET/POST/etc.)
43
+ def self.add_headers(request, additional_headers={})
44
+ request["User-Agent"] = "datarobot-ai/ruby"
45
+ request["Content-Type"] = "application/json"
46
+ request["Authorization"] = "Bearer #{@@api_key}"
47
+ additional_headers.each do |k, v|
48
+ request[k] = v
49
+ end
50
+ request
51
+ end
52
+
53
+ # Make a request with https. This is a helper method to avoid having to
54
+ # write net/http boilerplate
55
+ # @param [String] uri The uri to make the request to
56
+ # @param [Hash] params Additoinal params to add to the request. Will
57
+ # overwrite params in the query string of the given uri
58
+ #
59
+ # @yield [uri, http] Yields the https request with the parsed uri and http object
60
+ def self.with_https(uri, params={})
61
+ uri = URI.parse(uri)
62
+ existing_params = CGI::parse(uri.query.to_s)
63
+ uri.query = URI.encode_www_form(existing_params.merge params)
64
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
65
+ yield uri, http
66
+ end
67
+ end
68
+
69
+ # Gets a bare uri. Useful for requests with `result` objects
70
+ # @param [String] bare_uri
71
+ # @param [Block] block passed to `handle_response`
72
+ def self.get(bare_uri, &block)
73
+ with_https(bare_uri) do |uri, http|
74
+ request = Net::HTTP::Get.new(uri)
75
+ request = add_headers(request)
76
+
77
+ response = http.request(request)
78
+ handle_response(response, &block)
79
+ end
80
+ end
81
+
82
+ # Converts a file into an octet stream, and uploads it to given endpoint
83
+ # @param [String] endpoint The endpoint to upload the file to
84
+ # @param [String] file_path Path to local file for upload
85
+ # @return Delegates return values to `handle_response`
86
+ def self.upload_to_endpoint(endpoint, file_path, &block)
87
+ uri = "#{@@base_url}#{endpoint}"
88
+ with_https(uri) do |uri, http|
89
+ request = Net::HTTP::Post::Multipart.new uri.request_uri, "file" => UploadIO.new(file_path, "application/octet-stream")
90
+ request["User-Agent"] = "datarobot-ai/ruby"
91
+ request["Authorization"] = "Bearer #{@@api_key}"
92
+ response = http.request(request)
93
+ handle_response(response, &block)
94
+ end
95
+ end
96
+
97
+ # Common error handling and response parsing
98
+ # @yield [data] Yields the parsed json response if there is one
99
+ # @return nil if body is empty
100
+ # @return [Hash] data Parsed json response if no block is provided
101
+ def self.handle_response(response, &block)
102
+ if response.code.to_i < 300
103
+ return nil if response.body.to_s.empty?
104
+ data = JSON.parse(response.body)
105
+ if block_given?
106
+ yield data
107
+ else
108
+ data
109
+ end
110
+ else
111
+ determine_error(response)
112
+ end
113
+ end
114
+
115
+ # Makes a request to the given endpoint
116
+ # @param [String] endpoint The url path to the endpoint
117
+ # @param [String] method The HTTP method to make the request with
118
+ # @param [Hash] body JSON body. Only used in put and post requests
119
+ # @param [Hash] headers Additional headers to add to request
120
+ # @param [Hash] params Additoinal url parameters to add
121
+ # @return Delegates return values to `handle_response`
122
+ def self.request_endpoint(endpoint, method: 'get', body: {}, headers: {}, params: {}, &block)
123
+ uri_str = "#{@@base_url}#{endpoint}"
124
+ with_https(uri_str, params) do |uri, http|
125
+ case method.downcase
126
+ when 'put'
127
+ request = Net::HTTP::Put.new(uri.request_uri)
128
+ request.body = JSON.dump(body)
129
+ when 'delete'
130
+ request = Net::HTTP::Delete.new(uri.request_uri)
131
+ when 'post'
132
+ request = Net::HTTP::Post.new(uri.request_uri)
133
+ request.body = JSON.dump(body)
134
+ else
135
+ request = Net::HTTP::Get.new(uri)
136
+ end
137
+ request = add_headers(request, headers)
138
+
139
+ response = http.request(request)
140
+ handle_response(response, &block)
141
+ end
142
+ end
143
+
144
+ # Uses request data to determin error classes
145
+ # Only raises values. Will never return
146
+ def self.determine_error(response_obj)
147
+ parsed = JSON.parse(response_obj.body)
148
+ msg = parsed["error"] || parsed["message"]
149
+ case response_obj.code
150
+ when "403"
151
+ raise Datarobot::AiApi::UnauthorizedError, msg
152
+ when "404"
153
+ raise Datarobot::AiApi::NotFoundError, msg
154
+ else
155
+ raise Datarobot::AiApi::ApiError, msg
156
+ end
157
+ end
158
+
159
+ # Given an arbitrary url, return the correct AiApi object
160
+ # @param [String] url
161
+ # @return [Datarobot::AiApi::*]
162
+ def self.determine_object(url)
163
+ case url
164
+ when /\/ais/
165
+ Datarobot::AiApi::AI
166
+ when /\/datasets/
167
+ Datarobot::AiApi::Dataset
168
+ when /\/learningSessions/
169
+ Datarobot::AiApi::LearningSession
170
+ when /\/outputs/
171
+ Datarobot::AiApi::Output
172
+ when /\/status/
173
+ Datarobot::AiApi::Task
174
+ when /\/deployment/
175
+ Datarobot::AiApi::Deployment
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ require 'datarobot/ai_api/page'
182
+ require 'datarobot/ai_api/refreshable'
183
+ require 'datarobot/ai_api/evaluation'
184
+ require 'datarobot/ai_api/deployment'
185
+ require 'datarobot/ai_api/ai'
186
+ require 'datarobot/ai_api/dataset'
187
+ require 'datarobot/ai_api/task'
188
+ require 'datarobot/ai_api/learning_session'
189
+ require 'datarobot/ai_api/output'
190
+ require 'datarobot/ai_api/prediction'
@@ -0,0 +1,191 @@
1
+ module Datarobot
2
+ module AiApi
3
+
4
+ # This is the primary model in the AI API. This is the thing that learns
5
+ # about the data that you give it
6
+ class AI
7
+ include Datarobot::AiApi::Refreshable
8
+ attr_reader :output_count, :dataset_count, :learning_session_count, :id
9
+ attr_accessor :name,
10
+
11
+ # Retrieves all AIs as a paginated resource.
12
+ #
13
+ # @param [Integer] limit Number of AIs per page
14
+ # @param [Integer] offset How many AIs to skip before this page
15
+ #
16
+ # @return [Datarobot::AiApi::Page(Datarobot::AiApi::AI)]
17
+ def self.all(limit: 50, offset: 0)
18
+ Datarobot::AiApi.request_endpoint('/aiapi/ais/', params: {limit: limit, offset: offset}) do |data|
19
+ Datarobot::AiApi::Page.new(self, data)
20
+ end
21
+ end
22
+
23
+ # Retrieves an AI given an ID.
24
+ #
25
+ # @param [String] id The ID of the AI to retrieve
26
+ #
27
+ # @return [Datarobot::AiApi::AI]
28
+ def self.find(id, &block)
29
+ raise Datarobot::AiApi::NotFoundError, "Cannot find AI with id: nil" if id.nil?
30
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{id}") do |data|
31
+ if block_given?
32
+ yield data
33
+ else
34
+ self.new(data)
35
+ end
36
+ end
37
+ end
38
+
39
+ # Creates an AI
40
+ #
41
+ # @param [String] name The ID of the AI to retrieve
42
+ #
43
+ # @return [Datarobot::AiApi::AI]
44
+ def self.create(name: 'New AI')
45
+ Datarobot::AiApi.request_endpoint('/aiapi/ais/', method: 'post', body: { name: name }) do |data|
46
+ ai_data = Datarobot::AiApi.get(data["links"]["result"])
47
+ new(ai_data)
48
+ end
49
+ end
50
+
51
+ # Deletes an AI. Returns `nil` if the action was successful. Will raise
52
+ # an error if the action was unsuccessful
53
+ #
54
+ # @param [String] id The ID of the AI to delete
55
+ #
56
+ # @return [nil]
57
+ def self.delete(id)
58
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{id}", method: "delete")
59
+ end
60
+
61
+ # Given a parsed response body from the API, will create a new AI object
62
+ def initialize(options = {})
63
+ # Suppresses warnings about uninitialized variables
64
+ @id = nil
65
+ @name = nil
66
+ @dataset_count = nil
67
+ @output_count = nil
68
+ @learning_session_count = nil
69
+
70
+ set_from_options(options)
71
+ @outputs = nil
72
+ end
73
+
74
+ # Takes a response body from the API. Will set all AI attributes from the
75
+ # response body
76
+ #
77
+ # @param [Hash] options A parsed response body
78
+ # @return [void]
79
+ def set_from_options(options = {})
80
+ # one-liner replacement for `stringify_keys`
81
+ options = options.collect{|k,v| [k.to_s, v]}.to_h
82
+
83
+ @output_count = options.dig("outputCount") || @output_count
84
+ @dataset_count = options.dig("datasetCount") || @dataset_count
85
+ @learning_session_count = options.dig("learningSessionCount") || @learning_session_count
86
+ @name = options.dig("name") || @name
87
+ @id = options.dig("id") || @id
88
+ end
89
+
90
+ # Lists all the outputs associated with the AI as a paginated resource
91
+ #
92
+ # @return [Datarobot::AiApi::Page(Datarobot::AiApi::Output)]
93
+ def outputs
94
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{@id}/outputs") do |data|
95
+ data["aiId"] = @id
96
+ Datarobot::AiApi::Page.new(Datarobot::AiApi::Output, data)
97
+ end
98
+ end
99
+
100
+ # Lists all the datasets associated with the AI as a paginated resource
101
+ #
102
+ # @return [Datarobot::AiApi::Page(Datarobot::AiApi::Dataset)]
103
+ def datasets
104
+ Datarobot::AiApi.request_endpoint("/aiapi/datasets?aiId=#{@id}") do |data|
105
+ data["aiId"] = @id
106
+ Datarobot::AiApi::Page.new(Datarobot::AiApi::Dataset, data)
107
+ end
108
+ end
109
+
110
+ # Finds an output by name
111
+ #
112
+ # @param [String] name The name of the output associated with this AI
113
+ # that you want to find
114
+ # @return [Datarobot::AiApi::Output]
115
+ def find_output(name)
116
+ # TODO: Update this when AI API supports CGI escaped URIs
117
+ #
118
+ # This code was lifted from source of erb method:
119
+ # https://apidock.com/ruby/v2_6_3/ERB/Util/url_encode
120
+ #
121
+ # URI.escape is deprecated because it does not encode url control
122
+ # characters, thus making it a potential security issue and CGI.escape
123
+ # substitutes whitespace with a + which is correct, but unsupported by
124
+ # the AI API
125
+ encoded_name = name.gsub(/[^a-zA-Z0-9_\-.~]/) { |m|
126
+ sprintf("%%%02X", m.unpack1("C"))
127
+ }
128
+
129
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{@id}/outputs/#{encoded_name}") do |data|
130
+ data["aiId"] = @id
131
+ Datarobot::AiApi::Output.new(data)
132
+ end
133
+ end
134
+
135
+ # Adds a dataset to the AI. Raises an error on failure. Returns true on
136
+ # success
137
+ #
138
+ # @param [String] dataset_id The ID of the dataset to add
139
+ # @return [Bool] true
140
+ def add_dataset(dataset_id)
141
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{@id}/datasets", method: 'post', body: { datasetId: dataset_id })
142
+ true
143
+ end
144
+
145
+ # Adds a learning session to the AI. Raises an error on failure. Returns
146
+ # true on success
147
+ #
148
+ # @param [String] session_id The ID of the learning session to add
149
+ # @return [Bool] true
150
+ def add_learning_session(session_id)
151
+ Datarobot::AiApi.request_endpoint("/aiapi/ais/#{@id}/learningSessions", method: 'post', body: { learningSessionId: session_id })
152
+ true
153
+ end
154
+
155
+ # Trains an AI on the given target with the data in the file at the given
156
+ # path. Returns true if successful. Raises an error otherwise
157
+ #
158
+ # @param [String] target The target to train the AI on
159
+ # @return [Bool] true
160
+ def train(target, file_path)
161
+ dataset = Datarobot::AiApi::Dataset.create_from_file(file_path)
162
+ add_dataset(dataset.id)
163
+ learning_session = Datarobot::AiApi::LearningSession.create(dataset_id: dataset.id, target: target)
164
+ add_learning_session(learning_session.id)
165
+ Datarobot::AiApi::Output.create(ai_id: @id, learning_session_id: learning_session.id, output_name: "#{@name} output")
166
+ true
167
+ end
168
+
169
+ # Predicts a target feature given a data hash
170
+ #
171
+ # @param target The target feature to predict
172
+ # @param data The remaining features used to predict target feature
173
+ def predict(target, data={})
174
+ output = outputs.find { |o| o.target == target }
175
+
176
+ raise NotFoundError, "No output with target #{target.inspect} found AI with ID #{@id.inspect}" if output.nil?
177
+
178
+ deployment_id = output.source["deploymentId"]
179
+ key = output.source["datarobot-key"]
180
+ Datarobot::AiApi.request_endpoint(
181
+ "/predApi/v1.0/deployments/#{deployment_id}/predictions/",
182
+ method: 'post',
183
+ body: [ data ],
184
+ headers: {"datarobot-key" => key}
185
+ ) do |data|
186
+ Datarobot::AiApi::Page.new(Datarobot::AiApi::Prediction, data)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end