skylab_studio 1.0.5 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/skylab_studio/client.rb +313 -49
- data/lib/skylab_studio/config.rb +5 -2
- data/lib/skylab_studio/error.rb +2 -0
- data/lib/skylab_studio/request.rb +10 -8
- data/lib/skylab_studio/version.rb +3 -1
- data/lib/skylab_studio.rb +6 -4
- data/spec/lib/skylab_studio/client_spec.rb +70 -72
- data/spec/lib/skylab_studio/config_spec.rb +2 -0
- data/spec/lib/skylab_studio/request_spec.rb +17 -10
- data/spec/lib/skylab_studio/version_spec.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 549e0ebc3cedf74a848a299346f93ce8b77c6d8813383f7993c82e755fbf4d88
|
4
|
+
data.tar.gz: 32825398b0428f26bf8548b40a4eae238d24b97709ce408b021c343901a99835
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af7021375c6030fcf977ff50fd85f1a55947ff7d6a0f4c122b0d8dd55ed4265dc5cccfcb020f04636cda76310910fd09fc1dbcda2b0477937b761185d36f7e63
|
7
|
+
data.tar.gz: c5cac2e61a900dd0460e477c6c8ecf5a83d2ae3e69fb0503b780e71550f3c79f25e67e83cc49fdfd6dd7663ac4e558da0521eea7e2bd9d9b17f52aa8f92baf5f
|
data/lib/skylab_studio/client.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'error'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'digest'
|
6
|
+
require 'open-uri'
|
7
|
+
require 'base64'
|
8
|
+
require 'net/http'
|
9
|
+
require 'vips'
|
10
|
+
require 'concurrent'
|
2
11
|
|
3
12
|
module SkylabStudio
|
4
13
|
class ClientNilArgument < Error; end
|
@@ -25,109 +34,364 @@ module SkylabStudio
|
|
25
34
|
@configuration = SkylabStudio::Config.new(settings)
|
26
35
|
end
|
27
36
|
|
28
|
-
def list_jobs(
|
29
|
-
SkylabStudio::Request.new(@configuration).get(:jobs
|
37
|
+
def list_jobs()
|
38
|
+
SkylabStudio::Request.new(@configuration).get(:jobs)
|
30
39
|
end
|
31
40
|
|
32
41
|
def create_job(options = {})
|
33
|
-
validate_argument_presence options, :
|
42
|
+
validate_argument_presence options, :name
|
43
|
+
validate_argument_presence options, :profile_id
|
34
44
|
|
35
|
-
SkylabStudio::Request.new(@configuration).post(:jobs,
|
45
|
+
SkylabStudio::Request.new(@configuration).post(:jobs, options)
|
36
46
|
end
|
37
47
|
|
38
|
-
def get_job(
|
39
|
-
validate_argument_presence
|
48
|
+
def get_job(job_id)
|
49
|
+
validate_argument_presence nil, :job_id
|
40
50
|
|
41
|
-
SkylabStudio::Request.new(@configuration).get("jobs/#{
|
51
|
+
SkylabStudio::Request.new(@configuration).get("jobs/#{job_id}")
|
42
52
|
end
|
43
53
|
|
44
|
-
def
|
45
|
-
validate_argument_presence options, :
|
46
|
-
validate_argument_presence options, :job
|
54
|
+
def get_job_by_name(options = {})
|
55
|
+
validate_argument_presence options, :name
|
47
56
|
|
48
|
-
SkylabStudio::Request.new(@configuration).
|
57
|
+
SkylabStudio::Request.new(@configuration).get('jobs/find_by_name', options)
|
49
58
|
end
|
50
59
|
|
51
|
-
def
|
52
|
-
validate_argument_presence
|
60
|
+
def update_job(job_id, options = {})
|
61
|
+
validate_argument_presence nil, :job_id
|
53
62
|
|
54
|
-
SkylabStudio::Request.new(@configuration).
|
63
|
+
SkylabStudio::Request.new(@configuration).patch("jobs/#{job_id}", options)
|
55
64
|
end
|
56
65
|
|
57
|
-
def
|
66
|
+
def queue_job(options = {})
|
58
67
|
validate_argument_presence options, :id
|
59
68
|
|
60
|
-
SkylabStudio::Request.new(@configuration).post("jobs/#{options[:id]}/
|
69
|
+
SkylabStudio::Request.new(@configuration).post("jobs/#{options[:id]}/queue", options)
|
61
70
|
end
|
62
71
|
|
63
|
-
def
|
64
|
-
|
72
|
+
def fetch_jobs_in_front(job_id)
|
73
|
+
SkylabStudio::Request.new(@configuration).get("jobs/#{job_id}/jobs_in_front")
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_job(job_id)
|
77
|
+
validate_argument_presence nil, :job_id
|
65
78
|
|
66
|
-
SkylabStudio::Request.new(@configuration).
|
79
|
+
SkylabStudio::Request.new(@configuration).delete("jobs/#{job_id}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def cancel_job(job_id)
|
83
|
+
validate_argument_presence nil, :job_id
|
84
|
+
|
85
|
+
SkylabStudio::Request.new(@configuration).post("jobs/#{job_id}/cancel", nil)
|
67
86
|
end
|
68
87
|
|
69
88
|
def list_profiles(options = {})
|
70
|
-
SkylabStudio::Request.new(@configuration).get(:profiles,
|
89
|
+
SkylabStudio::Request.new(@configuration).get(:profiles, options)
|
71
90
|
end
|
72
91
|
|
73
92
|
def create_profile(options = {})
|
74
|
-
validate_argument_presence options, :
|
93
|
+
validate_argument_presence options, :name
|
75
94
|
|
76
|
-
SkylabStudio::Request.new(@configuration).post(:profiles,
|
95
|
+
SkylabStudio::Request.new(@configuration).post(:profiles, options)
|
77
96
|
end
|
78
97
|
|
79
|
-
def get_profile(
|
80
|
-
validate_argument_presence
|
98
|
+
def get_profile(profile_id)
|
99
|
+
validate_argument_presence nil, :profile_id
|
81
100
|
|
82
|
-
SkylabStudio::Request.new(@configuration).get("profiles/#{
|
101
|
+
SkylabStudio::Request.new(@configuration).get("profiles/#{profile_id}")
|
83
102
|
end
|
84
103
|
|
85
|
-
def update_profile(options = {})
|
86
|
-
validate_argument_presence
|
87
|
-
validate_argument_presence options, :profile
|
104
|
+
def update_profile(profile_id, options = {})
|
105
|
+
validate_argument_presence nil, :profile_id
|
88
106
|
|
89
|
-
SkylabStudio::Request.new(@configuration).patch("profiles/#{
|
107
|
+
SkylabStudio::Request.new(@configuration).patch("profiles/#{profile_id}", options)
|
90
108
|
end
|
91
109
|
|
92
|
-
def
|
93
|
-
validate_argument_presence
|
110
|
+
def upload_job_photo(photo_path = nil, job_id = nil)
|
111
|
+
validate_argument_presence nil, job_id
|
112
|
+
validate_argument_presence nil, photo_path
|
94
113
|
|
95
|
-
|
114
|
+
upload_photo(photo_path, job_id, 'job')
|
96
115
|
end
|
97
116
|
|
98
|
-
def
|
99
|
-
|
117
|
+
def upload_profile_photo(photo_path = nil, profile_id = nil)
|
118
|
+
validate_argument_presence nil, :photo_path
|
119
|
+
validate_argument_presence nil, :profile_id
|
120
|
+
|
121
|
+
upload_photo(photo_path, profile_id, 'profile')
|
100
122
|
end
|
101
123
|
|
102
|
-
def
|
103
|
-
validate_argument_presence
|
124
|
+
def get_photo(photo_id)
|
125
|
+
validate_argument_presence nil, :photo_id
|
104
126
|
|
105
|
-
SkylabStudio::Request.new(@configuration).
|
127
|
+
SkylabStudio::Request.new(@configuration).get("photos/#{photo_id}")
|
106
128
|
end
|
107
129
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
SkylabStudio::Request.new(@configuration).get("photos/#{options[:id]}", payload: options)
|
130
|
+
def get_job_photos(job_id)
|
131
|
+
SkylabStudio::Request.new(@configuration).get('photos/list_for_job', { job_id: job_id })
|
112
132
|
end
|
113
133
|
|
114
|
-
def
|
115
|
-
validate_argument_presence
|
116
|
-
validate_argument_presence options, :photo
|
134
|
+
def delete_photo(photo_id)
|
135
|
+
validate_argument_presence nil, :photo_id
|
117
136
|
|
118
|
-
SkylabStudio::Request.new(@configuration).
|
137
|
+
SkylabStudio::Request.new(@configuration).delete("photos/#{photo_id}")
|
119
138
|
end
|
120
139
|
|
121
|
-
def
|
122
|
-
|
140
|
+
def download_photo(photo_id, output_path, profile: nil, options: {})
|
141
|
+
file_name = ''
|
142
|
+
|
143
|
+
unless Dir.exist?(output_path)
|
144
|
+
# Must be a file path - separate output_path and file_name
|
145
|
+
file_name = File.basename(output_path)
|
146
|
+
output_path = File.dirname(output_path) || ''
|
147
|
+
end
|
148
|
+
|
149
|
+
begin
|
150
|
+
photo = get_photo(photo_id)
|
151
|
+
profile_id = photo['job']['profileId']
|
152
|
+
|
153
|
+
file_name = photo['name'] if file_name.empty?
|
154
|
+
|
155
|
+
profile ||= get_profile(profile_id)
|
156
|
+
|
157
|
+
is_extract = profile['enableExtract'].to_s == 'true'
|
158
|
+
replace_background = profile['replaceBackground'].to_s == 'true'
|
159
|
+
is_dual_file_output = profile['dualFileOutput'].to_s == 'true'
|
160
|
+
profile['enableStripPngMetadata'].to_s
|
161
|
+
bgs = options[:bgs]
|
162
|
+
|
163
|
+
# Load output image
|
164
|
+
image_buffer = download_image_async(photo['retouchedUrl'])
|
165
|
+
image = Vips::Image.new_from_buffer(image_buffer, '')
|
166
|
+
|
167
|
+
if is_extract # Output extract image
|
168
|
+
png_file_name = "#{File.basename(file_name, '.*')}.png"
|
123
169
|
|
124
|
-
|
170
|
+
# Dual File Output will provide an image in the format specified in the outputFileType field
|
171
|
+
# and an extracted image as a PNG.
|
172
|
+
image.write_to_file(File.join(output_path, png_file_name)) if is_dual_file_output
|
173
|
+
|
174
|
+
download_replaced_background_image(file_name, image, output_path, profile: profile, bgs: bgs) if replace_background
|
175
|
+
|
176
|
+
# Regular Extract output
|
177
|
+
image.write_to_file(File.join(output_path, png_file_name)) unless is_dual_file_output || replace_background
|
178
|
+
else # Non-extracted regular image output
|
179
|
+
image.write_to_file(File.join(output_path, file_name))
|
180
|
+
end
|
181
|
+
|
182
|
+
puts "Successfully downloaded: #{file_name}"
|
183
|
+
[file_name, true]
|
184
|
+
rescue StandardError => e
|
185
|
+
error_msg = "Failed to download photo id: #{photo_id} - #{e}"
|
186
|
+
raise error_msg if options[:return_on_error].nil?
|
187
|
+
|
188
|
+
[file_name, false]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def download_all_photos(photos_list, profile, output_path)
|
193
|
+
raise 'Invalid output path' unless Dir.exist?(output_path)
|
194
|
+
|
195
|
+
success_photos = []
|
196
|
+
errored_photos = []
|
197
|
+
bgs = []
|
198
|
+
|
199
|
+
begin
|
200
|
+
profile = get_profile(profile['id'])
|
201
|
+
bgs = download_bg_images(profile) if profile && profile['photos']&.any?
|
202
|
+
|
203
|
+
photo_ids = photos_list.map { |photo| photo['id'].to_s }.compact
|
204
|
+
|
205
|
+
pool = Concurrent::FixedThreadPool.new(@configuration.settings[:max_download_concurrency])
|
206
|
+
download_tasks = []
|
207
|
+
photo_options = { return_on_error: true, bgs: bgs }
|
208
|
+
photo_ids.each do |photo_id|
|
209
|
+
download_tasks << Concurrent::Future.execute(executor: pool) do
|
210
|
+
download_photo(photo_id.to_i, output_path, profile: profile, options: photo_options)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Wait for all download tasks to complete
|
215
|
+
results = download_tasks.map(&:value)
|
216
|
+
results.each do |result|
|
217
|
+
if result[1]
|
218
|
+
success_photos << result[0]
|
219
|
+
else
|
220
|
+
errored_photos << result[0]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
{ success_photos: success_photos, errored_photos: errored_photos }
|
225
|
+
rescue StandardError => e
|
226
|
+
warn e
|
227
|
+
|
228
|
+
{ success_photos: success_photos, errored_photos: errored_photos }
|
229
|
+
end
|
125
230
|
end
|
126
231
|
|
127
232
|
private
|
128
233
|
|
234
|
+
def get_upload_url(options = { use_cache_upload: false })
|
235
|
+
SkylabStudio::Request.new(@configuration).get('photos/upload_url', options)
|
236
|
+
end
|
237
|
+
|
238
|
+
def create_photo(options = {})
|
239
|
+
SkylabStudio::Request.new(@configuration).post(:photos, options)
|
240
|
+
end
|
241
|
+
|
242
|
+
def upload_photo(photo_path, id, model = 'job')
|
243
|
+
valid_exts_to_check = %w[.jpg .jpeg .png .webp]
|
244
|
+
|
245
|
+
raise 'Invalid file type: must be of type jpg/jpeg/png/webp' unless valid_exts_to_check.any? { |ext| photo_path.downcase.end_with?(ext) }
|
246
|
+
|
247
|
+
file_size = File.size(photo_path)
|
248
|
+
|
249
|
+
raise 'Invalid file size: must be no larger than 27MB' if file_size > 27 * 1024 * 1024
|
250
|
+
|
251
|
+
photo_name = File.basename(photo_path)
|
252
|
+
photo_ext = File.extname(photo_name)
|
253
|
+
headers = {}
|
254
|
+
md5hash = ''
|
255
|
+
|
256
|
+
# Read file contents to binary
|
257
|
+
data = nil
|
258
|
+
File.open(photo_path, 'rb') do |file|
|
259
|
+
data = file.read
|
260
|
+
md5hash = Digest::MD5.hexdigest(data)
|
261
|
+
end
|
262
|
+
|
263
|
+
# model - either job or profile (job_id/profile_id)
|
264
|
+
photo_data = { "#{model}_id": id, name: "#{photo_name}#{photo_ext}", path: photo_path, 'use_cache_upload': false }
|
265
|
+
|
266
|
+
if model == 'job'
|
267
|
+
job_type = get_job(id)['type']
|
268
|
+
|
269
|
+
headers = { 'X-Amz-Tagging' => 'job=photo&api=true' } if job_type == 'regular'
|
270
|
+
end
|
271
|
+
|
272
|
+
# Ask studio to create the photo record
|
273
|
+
photo_response_json = create_photo(photo_data)
|
274
|
+
|
275
|
+
unless photo_response_json
|
276
|
+
raise 'Unable to create the photo object, if creating profile photo, ensure enable_extract and replace_background is set to: True'
|
277
|
+
end
|
278
|
+
|
279
|
+
photo_id = photo_response_json['id']
|
280
|
+
|
281
|
+
b64md5 = Base64.strict_encode64([md5hash].pack('H*'))
|
282
|
+
payload = {
|
283
|
+
'use_cache_upload' => false,
|
284
|
+
'photo_id' => photo_id,
|
285
|
+
'content_md5' => b64md5
|
286
|
+
}
|
287
|
+
|
288
|
+
# Ask studio for a presigned url
|
289
|
+
upload_url_resp = get_upload_url(payload)
|
290
|
+
upload_url = upload_url_resp['url']
|
291
|
+
|
292
|
+
# PUT request to presigned url with image data
|
293
|
+
headers['Content-MD5'] = b64md5
|
294
|
+
|
295
|
+
begin
|
296
|
+
uri = URI(upload_url)
|
297
|
+
request = Net::HTTP::Put.new(uri, headers)
|
298
|
+
request.body = data
|
299
|
+
upload_photo_resp = Net::HTTP.start(uri.hostname) { |http| http.request(request) }
|
300
|
+
|
301
|
+
unless upload_photo_resp
|
302
|
+
puts 'First upload attempt failed, retrying...'
|
303
|
+
retry_count = 0
|
304
|
+
# Retry upload
|
305
|
+
|
306
|
+
while retry_count < 3
|
307
|
+
upload_photo_resp = Net::HTTP.start(uri.hostname) { |http| http.request(request) }
|
308
|
+
if upload_photo_resp
|
309
|
+
break # Upload was successful, exit the loop
|
310
|
+
elsif retry_count == 2 # Check if retry count is 2 (0-based indexing)
|
311
|
+
raise 'Unable to upload to the bucket after retrying.'
|
312
|
+
else
|
313
|
+
sleep(1) # Wait for a moment before retrying
|
314
|
+
retry_count += 1
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
rescue StandardError => e
|
319
|
+
puts "An exception of type #{e.class} occurred: #{e.message}"
|
320
|
+
puts 'Deleting created, but unuploaded photo...'
|
321
|
+
delete_photo({ id: photo_id }) if photo_id
|
322
|
+
end
|
323
|
+
|
324
|
+
photo_response_json
|
325
|
+
end
|
326
|
+
|
327
|
+
def download_image_async(image_url)
|
328
|
+
raise "Invalid retouchedUrl: \"#{image_url}\" - Please ensure the job is complete" unless image_url.start_with?('http', 'https')
|
329
|
+
|
330
|
+
begin
|
331
|
+
uri = URI(image_url)
|
332
|
+
response = Net::HTTP.get_response(uri)
|
333
|
+
|
334
|
+
if response.is_a?(Net::HTTPSuccess)
|
335
|
+
response.body
|
336
|
+
|
337
|
+
else
|
338
|
+
puts "Error downloading image: #{response.message}"
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
rescue StandardError => e
|
342
|
+
puts "Error downloading image: #{e.message}"
|
343
|
+
nil
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def download_bg_images(profile)
|
348
|
+
temp_bgs = []
|
349
|
+
|
350
|
+
bg_photos = profile['photos'].select { |photo| photo['jobId'].nil? }
|
351
|
+
|
352
|
+
bg_photos.each do |bg|
|
353
|
+
bg_buffer = download_image_async(bg['originalUrl'])
|
354
|
+
bg_image = Vips::Image.new_from_buffer(bg_buffer, '')
|
355
|
+
temp_bgs << bg_image
|
356
|
+
end
|
357
|
+
|
358
|
+
temp_bgs
|
359
|
+
end
|
360
|
+
|
361
|
+
def download_replaced_background_image(file_name, input_image, output_path, profile: nil, bgs: nil)
|
362
|
+
output_file_type = profile&.[]('outputFileType') || 'png'
|
363
|
+
|
364
|
+
bgs = download_bg_images(profile) if bgs.nil? && !profile.nil? && profile&.[]('photos')&.any?
|
365
|
+
|
366
|
+
alpha_channel = input_image[3]
|
367
|
+
rgb_channel = input_image[0..2]
|
368
|
+
rgb_cutout = rgb_channel.bandjoin(alpha_channel)
|
369
|
+
|
370
|
+
if bgs&.any?
|
371
|
+
bgs.each_with_index do |bg_image, i|
|
372
|
+
new_file_name = if i.zero?
|
373
|
+
"#{File.basename(file_name,
|
374
|
+
'.*')}.#{output_file_type}"
|
375
|
+
else
|
376
|
+
"#{File.basename(file_name,
|
377
|
+
'.*')} (#{i + 1}).#{output_file_type}"
|
378
|
+
end
|
379
|
+
resized_bg_image = bg_image.thumbnail_image(input_image.width, height: input_image.height, crop: :centre)
|
380
|
+
result_image = resized_bg_image.composite2(rgb_cutout, :over)
|
381
|
+
result_image.write_to_file(File.join(output_path, new_file_name))
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
true
|
386
|
+
rescue StandardError => e
|
387
|
+
error_msg = "Error downloading background image: #{e.message}"
|
388
|
+
raise error_msg
|
389
|
+
end
|
390
|
+
|
129
391
|
def validate_argument_presence(options, key)
|
130
|
-
raise SkylabStudio::ClientNilArgument, "#{key} cannot be nil" if options[key].nil?
|
392
|
+
raise SkylabStudio::ClientNilArgument, "#{key} cannot be nil" if options.is_a?(Hash) && options[key].nil?
|
393
|
+
|
394
|
+
raise SkylabStudio::ClientNilArgument, "#{key} cannot be nil" if key.nil?
|
131
395
|
end
|
132
396
|
end
|
133
397
|
end
|
data/lib/skylab_studio/config.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'version'
|
2
4
|
|
3
5
|
module SkylabStudio
|
4
6
|
class Config
|
5
7
|
attr_accessor :settings
|
6
8
|
|
7
|
-
DEFAULT_URL = 'https://studio.skylabtech.ai'
|
9
|
+
DEFAULT_URL = 'https://studio.skylabtech.ai'
|
8
10
|
|
9
11
|
def self.defaults
|
10
12
|
source = URI.parse(DEFAULT_URL)
|
@@ -17,7 +19,8 @@ module SkylabStudio
|
|
17
19
|
port: source.port,
|
18
20
|
api_version: 'v1',
|
19
21
|
debug: true,
|
20
|
-
client_stub: "ruby-#{VERSION}"
|
22
|
+
client_stub: "ruby-#{VERSION}",
|
23
|
+
max_download_concurrency: 5
|
21
24
|
}
|
22
25
|
end
|
23
26
|
|
data/lib/skylab_studio/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'error'
|
2
4
|
|
3
5
|
module SkylabStudio
|
@@ -37,7 +39,7 @@ module SkylabStudio
|
|
37
39
|
private
|
38
40
|
|
39
41
|
def request_path(end_point)
|
40
|
-
"/api/#{@configuration.api_version}/#{end_point}"
|
42
|
+
"/api/public/#{@configuration.api_version}/#{end_point}"
|
41
43
|
end
|
42
44
|
|
43
45
|
def use_ssl?
|
@@ -64,19 +66,19 @@ module SkylabStudio
|
|
64
66
|
end
|
65
67
|
|
66
68
|
def handle_response(response)
|
67
|
-
case
|
68
|
-
when Net::HTTPUnauthorized
|
69
|
+
case response
|
70
|
+
when Net::HTTPUnauthorized
|
69
71
|
raise SkylabStudio::ClientInvalidKey, 'Invalid api key'
|
70
|
-
when Net::HTTPForbidden
|
72
|
+
when Net::HTTPForbidden
|
71
73
|
raise SkylabStudio::ClientInvalidKey, 'Invalid api key'
|
72
|
-
when Net::HTTPNotFound
|
74
|
+
when Net::HTTPNotFound
|
73
75
|
raise SkylabStudio::ClientInvalidEndpoint, 'Resource not found'
|
74
|
-
when Net::HTTPBadRequest
|
76
|
+
when Net::HTTPBadRequest
|
75
77
|
raise SkylabStudio::ClientBadRequest, 'There was an error processing your request'
|
76
|
-
when Net::HTTPTooManyRequests
|
78
|
+
when Net::HTTPTooManyRequests
|
77
79
|
raise SkylabStudio::ClientBadRequest, 'The rate limit has been met'
|
78
80
|
when Net::HTTPSuccess
|
79
|
-
response
|
81
|
+
JSON.parse(response.body)
|
80
82
|
else
|
81
83
|
raise SkylabStudio::ClientUnknownError, 'An error has occurred'
|
82
84
|
end
|
data/lib/skylab_studio.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SkylabStudio
|
2
4
|
end
|
3
5
|
|
@@ -7,7 +9,7 @@ require 'net/https'
|
|
7
9
|
require 'uri'
|
8
10
|
require 'json'
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
require_relative 'skylab_studio/request'
|
13
|
+
require_relative 'skylab_studio/client'
|
14
|
+
require_relative 'skylab_studio/config'
|
15
|
+
require_relative 'skylab_studio/version' unless defined?(Skylab::VERSION)
|
@@ -1,11 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('../../../lib/skylab_studio.rb', __dir__)
|
2
4
|
|
3
5
|
RSpec.describe SkylabStudio::Client do
|
4
|
-
|
5
|
-
@client = SkylabStudio::Client.new
|
6
|
-
end
|
6
|
+
let(:client) { SkylabStudio::Client.new }
|
7
7
|
|
8
|
-
subject {
|
8
|
+
subject { client }
|
9
9
|
|
10
10
|
it { should respond_to(:list_jobs) }
|
11
11
|
it { should respond_to(:get_job) }
|
@@ -34,7 +34,7 @@ RSpec.describe SkylabStudio::Client do
|
|
34
34
|
it 'should return response' do
|
35
35
|
SkylabStudio::Request.any_instance.stub(:get).and_return(true)
|
36
36
|
|
37
|
-
|
37
|
+
subject.list_jobs.should eq(true)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -42,23 +42,19 @@ RSpec.describe SkylabStudio::Client do
|
|
42
42
|
it 'should return response' do
|
43
43
|
SkylabStudio::Request.any_instance.stub(:post).and_return(true)
|
44
44
|
|
45
|
-
|
46
|
-
job: {
|
47
|
-
profile_id: 1
|
48
|
-
}
|
49
|
-
).should eq(true)
|
45
|
+
subject.create_job({ name: 'test-job', profile_id: 1 }).should eq(true)
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
53
49
|
describe '#get_job' do
|
54
50
|
it 'should raise error with no id' do
|
55
|
-
expect {
|
51
|
+
expect { subject.get_job }.to raise_error(ArgumentError)
|
56
52
|
end
|
57
53
|
|
58
54
|
it 'should return response' do
|
59
55
|
SkylabStudio::Request.any_instance.stub(:get).and_return(true)
|
60
56
|
|
61
|
-
|
57
|
+
subject.get_job(123).should eq(true)
|
62
58
|
end
|
63
59
|
end
|
64
60
|
|
@@ -66,12 +62,7 @@ RSpec.describe SkylabStudio::Client do
|
|
66
62
|
it 'should return response' do
|
67
63
|
SkylabStudio::Request.any_instance.stub(:patch).and_return(true)
|
68
64
|
|
69
|
-
|
70
|
-
id: 1,
|
71
|
-
job: {
|
72
|
-
profile_id: 2
|
73
|
-
}
|
74
|
-
).should eq(true)
|
65
|
+
subject.update_job(1, { profile_id: 2 }).should eq(true)
|
75
66
|
end
|
76
67
|
end
|
77
68
|
|
@@ -79,17 +70,15 @@ RSpec.describe SkylabStudio::Client do
|
|
79
70
|
it 'should return response' do
|
80
71
|
SkylabStudio::Request.any_instance.stub(:delete).and_return(true)
|
81
72
|
|
82
|
-
|
83
|
-
id: 1
|
84
|
-
).should eq(true)
|
73
|
+
subject.delete_job(1).should eq(true)
|
85
74
|
end
|
86
75
|
end
|
87
76
|
|
88
|
-
describe '#
|
77
|
+
describe '#queue_job' do
|
89
78
|
it 'should return response' do
|
90
79
|
SkylabStudio::Request.any_instance.stub(:post).and_return(true)
|
91
80
|
|
92
|
-
|
81
|
+
subject.queue_job(
|
93
82
|
id: 1
|
94
83
|
).should eq(true)
|
95
84
|
end
|
@@ -99,9 +88,7 @@ RSpec.describe SkylabStudio::Client do
|
|
99
88
|
it 'should return response' do
|
100
89
|
SkylabStudio::Request.any_instance.stub(:post).and_return(true)
|
101
90
|
|
102
|
-
|
103
|
-
id: 1
|
104
|
-
).should eq(true)
|
91
|
+
subject.cancel_job(1).should eq(true)
|
105
92
|
end
|
106
93
|
end
|
107
94
|
|
@@ -109,7 +96,7 @@ RSpec.describe SkylabStudio::Client do
|
|
109
96
|
it 'should return response' do
|
110
97
|
SkylabStudio::Request.any_instance.stub(:get).and_return(true)
|
111
98
|
|
112
|
-
|
99
|
+
subject.list_profiles.should eq(true)
|
113
100
|
end
|
114
101
|
end
|
115
102
|
|
@@ -117,8 +104,8 @@ RSpec.describe SkylabStudio::Client do
|
|
117
104
|
it 'should return response' do
|
118
105
|
SkylabStudio::Request.any_instance.stub(:post).and_return(true)
|
119
106
|
|
120
|
-
|
121
|
-
|
107
|
+
subject.create_profile(
|
108
|
+
{
|
122
109
|
name: 'Foo'
|
123
110
|
}
|
124
111
|
).should eq(true)
|
@@ -127,13 +114,13 @@ RSpec.describe SkylabStudio::Client do
|
|
127
114
|
|
128
115
|
describe '#get_profile' do
|
129
116
|
it 'should raise error with no id' do
|
130
|
-
expect {
|
117
|
+
expect { subject.get_profile }.to raise_error(ArgumentError)
|
131
118
|
end
|
132
119
|
|
133
120
|
it 'should return response' do
|
134
121
|
SkylabStudio::Request.any_instance.stub(:get).and_return(true)
|
135
122
|
|
136
|
-
|
123
|
+
subject.get_profile(123).should eq(true)
|
137
124
|
end
|
138
125
|
end
|
139
126
|
|
@@ -141,67 +128,80 @@ RSpec.describe SkylabStudio::Client do
|
|
141
128
|
it 'should return response' do
|
142
129
|
SkylabStudio::Request.any_instance.stub(:patch).and_return(true)
|
143
130
|
|
144
|
-
|
145
|
-
id: 1,
|
146
|
-
profile: {
|
147
|
-
name: 'Bar'
|
148
|
-
}
|
149
|
-
).should eq(true)
|
131
|
+
subject.update_profile(1, { name: 'Bar' } ).should eq(true)
|
150
132
|
end
|
151
133
|
end
|
152
134
|
|
153
|
-
describe '#
|
154
|
-
|
155
|
-
|
135
|
+
describe '#upload_job_photo' do
|
136
|
+
before do
|
137
|
+
stub_request(:post, 'https://studio.skylabtech.ai/api/public/v1/photos')
|
138
|
+
.to_return(status: 200, body: { id: 1, photo: {} }.to_json, headers: {})
|
156
139
|
|
157
|
-
|
158
|
-
|
159
|
-
|
140
|
+
stub_request(:get, 'https://studio.skylabtech.ai/api/public/v1/photos/upload_url')
|
141
|
+
.to_return(status: 200, body: { url: 'http://test.test/' }.to_json, headers: {})
|
142
|
+
|
143
|
+
stub_request(:get, 'https://studio.skylabtech.ai/api/public/v1/jobs/1')
|
144
|
+
.to_return(status: 200, body: {}.to_json, headers: {})
|
145
|
+
|
146
|
+
stub_request(:delete, 'https://studio.skylabtech.ai/api/public/v1/photos/1')
|
147
|
+
.to_return(status: 200, body: { id: 1 }.to_json, headers: {})
|
148
|
+
|
149
|
+
stub_request(:put, /.*/)
|
150
|
+
.to_return(status: 200, body: {}.to_json, headers: {})
|
151
|
+
|
152
|
+
# allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { { id: 1 }.to_json }
|
160
153
|
end
|
161
|
-
end
|
162
154
|
|
163
|
-
describe '#list_photos' do
|
164
155
|
it 'should return response' do
|
165
|
-
|
156
|
+
photo_path = "#{File.expand_path('../../', File.dirname(__FILE__))}/test-portrait-1.JPG"
|
157
|
+
id = 1
|
158
|
+
|
159
|
+
expected_response = { id: id, photo: {} }.to_json
|
166
160
|
|
167
|
-
|
161
|
+
expect(subject.upload_job_photo(photo_path, id).to_json).to eq(expected_response)
|
168
162
|
end
|
169
163
|
end
|
170
164
|
|
171
|
-
describe '#
|
165
|
+
describe '#upload_profile_photo' do
|
166
|
+
before do
|
167
|
+
stub_request(:post, 'https://studio.skylabtech.ai/api/public/v1/photos')
|
168
|
+
.to_return(status: 200, body: { id: 1, photo: {} }.to_json, headers: {})
|
169
|
+
|
170
|
+
stub_request(:get, 'https://studio.skylabtech.ai/api/public/v1/photos/upload_url')
|
171
|
+
.to_return(status: 200, body: { url: 'http://test.test/' }.to_json, headers: {})
|
172
|
+
|
173
|
+
stub_request(:get, 'https://studio.skylabtech.ai/api/public/v1/profiles/upload_url')
|
174
|
+
.to_return(status: 200, body: { url: 'http://test.test/' }.to_json, headers: {})
|
175
|
+
|
176
|
+
stub_request(:get, 'https://studio.skylabtech.ai/api/public/v1/jobs/1')
|
177
|
+
.to_return(status: 200, body: {}.to_json, headers: {})
|
178
|
+
|
179
|
+
stub_request(:delete, 'https://studio.skylabtech.ai/api/public/v1/photos/1')
|
180
|
+
.to_return(status: 200, body: { id: 1 }.to_json, headers: {})
|
181
|
+
|
182
|
+
stub_request(:put, /.*/)
|
183
|
+
.to_return(status: 200, body: {}.to_json, headers: {})
|
184
|
+
end
|
185
|
+
|
172
186
|
it 'should return response' do
|
173
|
-
|
187
|
+
photo_path = "#{File.expand_path('../../', File.dirname(__FILE__))}/test-portrait-1.JPG"
|
188
|
+
id = 1
|
174
189
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
}
|
179
|
-
).should eq(true)
|
190
|
+
expected_response = { id: id, photo: {} }.to_json
|
191
|
+
|
192
|
+
expect(subject.upload_profile_photo(photo_path, 1).to_json).to eq(expected_response)
|
180
193
|
end
|
181
194
|
end
|
182
195
|
|
183
196
|
describe '#get_photo' do
|
184
197
|
it 'should raise error with no id' do
|
185
|
-
expect {
|
198
|
+
expect { subject.get_photo }.to raise_error(ArgumentError)
|
186
199
|
end
|
187
200
|
|
188
201
|
it 'should return response' do
|
189
202
|
SkylabStudio::Request.any_instance.stub(:get).and_return(true)
|
190
203
|
|
191
|
-
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
describe '#update_photo' do
|
196
|
-
it 'should return response' do
|
197
|
-
SkylabStudio::Request.any_instance.stub(:patch).and_return(true)
|
198
|
-
|
199
|
-
@client.update_photo(
|
200
|
-
id: 1,
|
201
|
-
photo: {
|
202
|
-
name: 'Bar'
|
203
|
-
}
|
204
|
-
).should eq(true)
|
204
|
+
subject.get_photo(123).should eq(true)
|
205
205
|
end
|
206
206
|
end
|
207
207
|
|
@@ -209,9 +209,7 @@ RSpec.describe SkylabStudio::Client do
|
|
209
209
|
it 'should return response' do
|
210
210
|
SkylabStudio::Request.any_instance.stub(:delete).and_return(true)
|
211
211
|
|
212
|
-
|
213
|
-
id: 1
|
214
|
-
).should eq(true)
|
212
|
+
subject.delete_photo(1).should eq(true)
|
215
213
|
end
|
216
214
|
end
|
217
215
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('../../../lib/skylab_studio.rb', __dir__)
|
2
4
|
|
3
5
|
RSpec.describe SkylabStudio::Request do
|
@@ -12,9 +14,10 @@ RSpec.describe SkylabStudio::Request do
|
|
12
14
|
|
13
15
|
describe '#post' do
|
14
16
|
it 'should return success' do
|
15
|
-
Net::HTTP.
|
17
|
+
allow_any_instance_of(Net::HTTP).to receive(:request) { Net::HTTPSuccess.new(1.0, 200, 'OK') }
|
18
|
+
allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { {}.to_json }
|
16
19
|
|
17
|
-
@request.post(:jobs, {}).should be_instance_of(
|
20
|
+
@request.post(:jobs, {}).should be_instance_of(Hash)
|
18
21
|
end
|
19
22
|
|
20
23
|
it 'should raise error on 401' do
|
@@ -62,9 +65,10 @@ RSpec.describe SkylabStudio::Request do
|
|
62
65
|
|
63
66
|
describe '#get' do
|
64
67
|
it 'should return success' do
|
65
|
-
Net::HTTP.
|
68
|
+
allow_any_instance_of(Net::HTTP).to receive(:request) { Net::HTTPSuccess.new(1.0, 200, 'OK') }
|
69
|
+
allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { {}.to_json }
|
66
70
|
|
67
|
-
@request.get(:jobs, {}).should be_instance_of(
|
71
|
+
@request.get(:jobs, {}).should be_instance_of(Hash)
|
68
72
|
end
|
69
73
|
|
70
74
|
it 'should raise error on 401' do
|
@@ -112,9 +116,10 @@ RSpec.describe SkylabStudio::Request do
|
|
112
116
|
|
113
117
|
describe '#delete' do
|
114
118
|
it 'should return success' do
|
115
|
-
Net::HTTP.
|
119
|
+
allow_any_instance_of(Net::HTTP).to receive(:request) { Net::HTTPSuccess.new(1.0, 200, 'OK') }
|
120
|
+
allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { {}.to_json }
|
116
121
|
|
117
|
-
@request.delete(:jobs).should be_instance_of(
|
122
|
+
@request.delete(:jobs).should be_instance_of(Hash)
|
118
123
|
end
|
119
124
|
|
120
125
|
it 'should raise error on 401' do
|
@@ -162,9 +167,10 @@ RSpec.describe SkylabStudio::Request do
|
|
162
167
|
|
163
168
|
describe '#put' do
|
164
169
|
it 'should return success' do
|
165
|
-
Net::HTTP.
|
170
|
+
allow_any_instance_of(Net::HTTP).to receive(:request) { Net::HTTPSuccess.new(1.0, 200, 'OK') }
|
171
|
+
allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { {}.to_json }
|
166
172
|
|
167
|
-
@request.put(:jobs, {}).should be_instance_of(
|
173
|
+
@request.put(:jobs, {}).should be_instance_of(Hash)
|
168
174
|
end
|
169
175
|
|
170
176
|
it 'should raise error on 401' do
|
@@ -212,9 +218,10 @@ RSpec.describe SkylabStudio::Request do
|
|
212
218
|
|
213
219
|
describe '#patch' do
|
214
220
|
it 'should return success' do
|
215
|
-
Net::HTTP.
|
221
|
+
allow_any_instance_of(Net::HTTP).to receive(:request) { Net::HTTPSuccess.new(1.0, 200, 'OK') }
|
222
|
+
allow_any_instance_of(Net::HTTPSuccess).to receive(:body) { {}.to_json }
|
216
223
|
|
217
|
-
@request.patch(:jobs, {}).should be_instance_of(
|
224
|
+
@request.patch(:jobs, {}).should be_instance_of(Hash)
|
218
225
|
end
|
219
226
|
|
220
227
|
it 'should raise error on 401' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skylab_studio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Lam
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 3.12.0
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 3.12.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: shoulda-matchers
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.
|
33
|
+
version: 5.3.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.
|
40
|
+
version: 5.3.0
|
41
41
|
description: studio.skylabtech.ai ruby client
|
42
42
|
email:
|
43
43
|
- info@skylabtech.ai
|
@@ -60,7 +60,7 @@ homepage: https://github.com/skylab-tech/studio_client_ruby
|
|
60
60
|
licenses:
|
61
61
|
- Apache-2.0
|
62
62
|
metadata: {}
|
63
|
-
post_install_message:
|
63
|
+
post_install_message:
|
64
64
|
rdoc_options: []
|
65
65
|
require_paths:
|
66
66
|
- lib
|
@@ -68,7 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version:
|
71
|
+
version: 2.7.0
|
72
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
@@ -76,12 +76,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
76
|
version: '0'
|
77
77
|
requirements: []
|
78
78
|
rubygems_version: 3.1.6
|
79
|
-
signing_key:
|
79
|
+
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: A HTTP client for accessing studio.skylabtech.ai services.
|
82
82
|
test_files:
|
83
83
|
- spec/spec_helper.rb
|
84
|
-
- spec/lib/skylab_studio/
|
84
|
+
- spec/lib/skylab_studio/request_spec.rb
|
85
85
|
- spec/lib/skylab_studio/client_spec.rb
|
86
|
+
- spec/lib/skylab_studio/config_spec.rb
|
86
87
|
- spec/lib/skylab_studio/version_spec.rb
|
87
|
-
- spec/lib/skylab_studio/request_spec.rb
|