datarobot-ai_api 0.0.3

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