skylab_studio 1.0.5 → 1.0.7
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.
- 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
|