shotgun_api_ruby 0.0.7 → 0.1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,8 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
3
+ require 'bundler/gem_tasks'
data/bin/console CHANGED
@@ -1,17 +1,26 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "shotgun_api_ruby"
4
+ require 'bundler/setup'
5
+ require 'shotgun_api_ruby'
6
6
 
7
7
  require 'dotenv/load'
8
8
 
9
9
  if ENV['SITE_NAME'] && ENV['SCRIPT_NAME'] && ENV['SCRIPT_KEY']
10
- $cl = ShotgunApiRuby.new(
11
- shotgun_site: ENV['SITE_NAME'],
12
- auth: { client_id: ENV['SCRIPT_NAME'], client_secret: ENV['SCRIPT_KEY'] }
13
- )
10
+ $cl =
11
+ ShotgunApiRuby.new(
12
+ shotgun_site: ENV['SITE_NAME'],
13
+ auth: { client_id: ENV['SCRIPT_NAME'], client_secret: ENV['SCRIPT_KEY'] },
14
+ )
14
15
  end
16
+ if ENV['SITE_URL'] && ENV['SCRIPT_NAME'] && ENV['SCRIPT_KEY']
17
+ $cl =
18
+ ShotgunApiRuby.new(
19
+ site_url: ENV['SITE_URL'],
20
+ auth: { client_id: ENV['SCRIPT_NAME'], client_secret: ENV['SCRIPT_KEY'] },
21
+ )
22
+ end
23
+
15
24
  # You can add fixtures and/or initialization code here to make experimenting
16
25
  # with your gem easier. You can also use a different console, if you like.
17
26
 
@@ -19,5 +28,5 @@ end
19
28
  # require "pry"
20
29
  # Pry.start
21
30
 
22
- require "pry"
31
+ require 'pry'
23
32
  Pry.start(__FILE__)
data/bin/prettirun ADDED
@@ -0,0 +1 @@
1
+ yarn prettier -c './**/*.rb'
data/bin/ruborun ADDED
@@ -0,0 +1 @@
1
+ bundle exec rubocop -P
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # zeitwerk will take care of auto loading files based on their name :)
4
- require "zeitwerk"
4
+ require 'zeitwerk'
5
5
  require 'active_support/core_ext/string/inflections'
6
6
  require 'ostruct'
7
- require "faraday"
7
+ require 'faraday'
8
8
  require 'json'
9
9
 
10
10
  loader = Zeitwerk::Loader.for_gem
@@ -15,3 +15,5 @@ module ShotgunApiRuby
15
15
  Client.new(**args)
16
16
  end
17
17
  end
18
+
19
+ loader.eager_load
@@ -1,20 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShotgunApiRuby
4
+ # Faraday middleware responsible for authentication with
5
+ # the shotgun site
4
6
  class Auth < Faraday::Middleware
7
+ # Validate auth parameters format
5
8
  module Validator
6
- def self.valid?(auth)
7
- (auth[:client_id] && auth[:client_secret]) ||
8
- (auth[:password] && auth[:username]) ||
9
- auth[:session_token] ||
10
- auth[:refresh_token]
9
+ # Validate auth parameters format
10
+ #
11
+ # @param []
12
+ def self.valid?(
13
+ client_id: nil,
14
+ client_secret: nil,
15
+ username: nil,
16
+ password: nil,
17
+ session_token: nil,
18
+ refresh_token: nil
19
+ )
20
+ (client_id && client_secret) || (password && username) ||
21
+ session_token || refresh_token
11
22
  end
12
23
  end
13
24
 
14
25
  def initialize(app = nil, options = {})
15
- raise "missing auth" unless options[:auth]
16
- raise "missing site_url" unless options[:site_url]
17
- raise "Auth not valid" unless Validator.valid?(options[:auth])
26
+ raise 'missing auth' unless options[:auth]
27
+ raise 'missing site_url' unless options[:site_url]
28
+ unless Validator.valid?(**options[:auth]&.transform_keys(&:to_sym))
29
+ raise 'Auth not valid'
30
+ end
18
31
 
19
32
  super(app)
20
33
 
@@ -27,19 +40,25 @@ module ShotgunApiRuby
27
40
  @refresh_token = options[:auth][:refresh_token]
28
41
  end
29
42
 
30
- attr_reader :client_id, :client_secret, :site_url, :username, :password, :session_token, :refresh_token
43
+ attr_reader :client_id,
44
+ :client_secret,
45
+ :site_url,
46
+ :username,
47
+ :password,
48
+ :session_token,
49
+ :refresh_token
31
50
 
32
51
  def auth_type
33
52
  @auth_type ||=
34
53
  begin
35
- if client_id
54
+ if refresh_token
55
+ 'refresh_token'
56
+ elsif client_id
36
57
  'client_credentials'
37
58
  elsif username
38
59
  'password'
39
60
  elsif session_token
40
61
  'session_token'
41
- elsif refresh_token
42
- 'refresh_token'
43
62
  end
44
63
  end
45
64
  end
@@ -56,16 +75,18 @@ module ShotgunApiRuby
56
75
  @auth_params ||=
57
76
  begin
58
77
  case auth_type
78
+ when 'refresh_token'
79
+ "refresh_token=#{refresh_token}&grant_type=refresh_token"
59
80
  when 'client_credentials'
60
- "client_id=#{client_id}&client_secret=#{client_secret}&grant_type=client_credentials"
81
+ "client_id=#{client_id}&client_secret=#{
82
+ client_secret
83
+ }&grant_type=client_credentials"
61
84
  when 'password'
62
85
  "username=#{username}&password=#{password}&grant_type=password"
63
86
  when 'session_token'
64
87
  "session_token=#{session_token}&grant_type=session_token"
65
- when 'refresh_token'
66
- "refresh_token=#{refresh_token}&grant_type=refresh_token"
67
88
  else
68
- raise "Not a valid/implemented auth type"
89
+ raise 'Not a valid/implemented auth type'
69
90
  end
70
91
  end
71
92
  end
@@ -81,8 +102,8 @@ module ShotgunApiRuby
81
102
  def sign_in
82
103
  resp =
83
104
  Faraday.post(auth_url) do |req|
84
- req.headers["Content-Type"] = 'application/x-www-form-urlencoded'
85
- req.headers["Accept"] = "application/json"
105
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
106
+ req.headers['Accept'] = 'application/json'
86
107
  end
87
108
  resp_body = JSON.parse(resp.body)
88
109
 
@@ -95,8 +116,8 @@ module ShotgunApiRuby
95
116
 
96
117
  def std_headers
97
118
  {
98
- "Accept" => "application/json",
99
- "Authorization" => "Bearer #{access_token}",
119
+ 'Accept' => 'application/json',
120
+ 'Authorization' => "Bearer #{access_token}",
100
121
  }
101
122
  end
102
123
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShotgunApiRuby
4
+ # Main class for connection.
5
+ #
6
+ # This should be only instanciated once to re-use tokens
4
7
  class Client
8
+ # Faraday connection
5
9
  attr_reader :connection
6
10
 
7
11
  def initialize(auth:, site_url: nil, shotgun_site: nil)
8
- raise "No site given" unless site_url || shotgun_site
9
- raise "auth param not valid" unless auth && Auth::Validator.valid?(auth)
12
+ raise 'No site given' unless site_url || shotgun_site
13
+ raise 'auth param not valid' unless auth && Auth::Validator.valid?(**auth)
10
14
 
11
15
  site_url ||= "https://#{shotgun_site}.shotgunstudio.com/api/v1"
12
16
  @connection =
@@ -16,14 +20,17 @@ module ShotgunApiRuby
16
20
  end
17
21
  end
18
22
 
23
+ # Access preferences APIs
19
24
  def preferences
20
25
  @preferences = Preferences.new(connection)
21
26
  end
22
27
 
28
+ # Access server_info APIs
23
29
  def server_info
24
30
  @server_info || ServerInfo.new(connection)
25
31
  end
26
32
 
33
+ # Access entities related APIs
27
34
  def entities(type)
28
35
  public_send(type)
29
36
  end
@@ -34,16 +41,19 @@ module ShotgunApiRuby
34
41
 
35
42
  def method_missing(name, *args, &block)
36
43
  if args.empty?
37
- name = formated_name(name)
38
- define_singleton_method(name) do
39
- if entities_client = instance_variable_get("@#{name}")
40
- entities_client
41
- else
42
- entities_client = entities_aux(name)
43
- instance_variable_set("@#{name}", entities_client)
44
+ fname = formated_name(name)
45
+ self
46
+ .class
47
+ .define_method(fname) do
48
+ if entities_client = instance_variable_get("@#{fname}")
49
+ entities_client
50
+ else
51
+ entities_client = entities_aux(fname)
52
+ instance_variable_set("@#{fname}", entities_client)
53
+ end
44
54
  end
45
- end
46
- send(name)
55
+ self.class.instance_eval { alias_method name, fname }
56
+ send(fname)
47
57
  else
48
58
  super
49
59
  end
@@ -5,6 +5,7 @@ module ShotgunApiRuby
5
5
  def initialize(connection, type)
6
6
  @connection = connection.dup
7
7
  @type = type
8
+ @base_url_prefix = @connection.url_prefix
8
9
  @connection.url_prefix = "#{@connection.url_prefix}/entity/#{type}"
9
10
  end
10
11
 
@@ -25,7 +26,7 @@ module ShotgunApiRuby
25
26
  retired: retired,
26
27
  logical_operator: logical_operator,
27
28
  include_archived_projects: include_archived_projects,
28
- page_size: 1
29
+ page_size: 1,
29
30
  ).first
30
31
  end
31
32
 
@@ -42,17 +43,17 @@ module ShotgunApiRuby
42
43
  raise "Error while getting #{type}: #{resp_body['errors']}"
43
44
  end
44
45
 
45
- entity = resp_body["data"]
46
+ entity = resp_body['data']
46
47
  Entity.new(
47
48
  entity['type'],
48
49
  OpenStruct.new(entity['attributes']),
49
50
  entity['relationships'],
50
51
  entity['id'],
51
- entity['links']
52
+ entity['links'],
52
53
  )
53
54
  end
54
55
 
55
- def create(**attributes)
56
+ def create(attributes)
56
57
  resp =
57
58
  @connection.post('', attributes.to_json) do |req|
58
59
  req.headers['Content-Type'] = 'application/json'
@@ -61,20 +62,22 @@ module ShotgunApiRuby
61
62
  resp_body = JSON.parse(resp.body)
62
63
 
63
64
  if resp.status >= 300
64
- raise "Error while creating #{type}# with #{attributes}: #{resp_body['errors']}"
65
+ raise "Error while creating #{type} with #{attributes}: #{
66
+ resp_body['errors']
67
+ }"
65
68
  end
66
69
 
67
- entity = resp_body["data"]
70
+ entity = resp_body['data']
68
71
  Entity.new(
69
72
  entity['type'],
70
73
  OpenStruct.new(entity['attributes']),
71
74
  entity['relationships'],
72
75
  entity['id'],
73
- entity['links']
76
+ entity['links'],
74
77
  )
75
78
  end
76
79
 
77
- def update(id, **changes)
80
+ def update(id, changes)
78
81
  return find(id) if changes.empty?
79
82
 
80
83
  resp =
@@ -85,16 +88,18 @@ module ShotgunApiRuby
85
88
  resp_body = JSON.parse(resp.body)
86
89
 
87
90
  if resp.status >= 300
88
- raise "Error while updating #{type}##{id} with #{changes}: #{resp_body['errors']}"
91
+ raise "Error while updating #{type}##{id} with #{changes}: #{
92
+ resp_body['errors']
93
+ }"
89
94
  end
90
95
 
91
- entity = resp_body["data"]
96
+ entity = resp_body['data']
92
97
  Entity.new(
93
98
  entity['type'],
94
99
  OpenStruct.new(entity['attributes']),
95
100
  entity['relationships'],
96
101
  entity['id'],
97
- entity['links']
102
+ entity['links'],
98
103
  )
99
104
  end
100
105
 
@@ -113,8 +118,7 @@ module ShotgunApiRuby
113
118
  end
114
119
 
115
120
  def revive(id)
116
- resp =
117
- @connection.post("#{id}?revive=true")
121
+ resp = @connection.post("#{id}?revive=true")
118
122
 
119
123
  if resp.status >= 300
120
124
  resp_body = JSON.parse(resp.body)
@@ -134,16 +138,18 @@ module ShotgunApiRuby
134
138
  retired: nil,
135
139
  include_archived_projects: nil
136
140
  )
137
- if filter && !filters_are_simple?(filter)
138
- return search(
139
- fields: fields,
140
- logical_operator: logical_operator,
141
- sort: sort,
142
- filter: filter,
143
- page: page,
144
- page_size: page_size,
145
- retired: retired,
146
- include_archived_projects: include_archived_projects
141
+ if filter && !Params.filters_are_simple?(filter)
142
+ return(
143
+ search(
144
+ fields: fields,
145
+ logical_operator: logical_operator,
146
+ sort: sort,
147
+ filter: filter,
148
+ page: page,
149
+ page_size: page_size,
150
+ retired: retired,
151
+ include_archived_projects: include_archived_projects,
152
+ )
147
153
  )
148
154
  end
149
155
 
@@ -162,13 +168,13 @@ module ShotgunApiRuby
162
168
  raise "Error while getting #{type}: #{resp_body['errors']}"
163
169
  end
164
170
 
165
- resp_body["data"].map do |entity|
171
+ resp_body['data'].map do |entity|
166
172
  Entity.new(
167
173
  entity['type'],
168
174
  OpenStruct.new(entity['attributes']),
169
175
  entity['relationships'],
170
176
  entity['id'],
171
- entity['links']
177
+ entity['links'],
172
178
  )
173
179
  end
174
180
  end
@@ -183,16 +189,18 @@ module ShotgunApiRuby
183
189
  retired: nil,
184
190
  include_archived_projects: nil
185
191
  )
186
- if filter.nil? || filters_are_simple?(filter)
187
- return all(
188
- fields: fields,
189
- logical_operator: logical_operator,
190
- sort: sort,
191
- filter: filter,
192
- page: page,
193
- page_size: page_size,
194
- retired: retired,
195
- include_archived_projects: include_archived_projects
192
+ if filter.nil? || Params.filters_are_simple?(filter)
193
+ return(
194
+ all(
195
+ fields: fields,
196
+ logical_operator: logical_operator,
197
+ sort: sort,
198
+ filter: filter,
199
+ page: page,
200
+ page_size: page_size,
201
+ retired: retired,
202
+ include_archived_projects: include_archived_projects,
203
+ )
196
204
  )
197
205
  end
198
206
  params = Params.new
@@ -201,26 +209,21 @@ module ShotgunApiRuby
201
209
  params.add_sort(sort)
202
210
  params.add_page(page, page_size)
203
211
  params.add_options(retired, include_archived_projects)
204
- new_filter = {}
205
- if filter.is_a?(Hash)
206
- new_filter[:conditions] =
207
- (filter[:conditions] || translate_complex_to_sg_filters(filter))
208
- new_filter[:logical_operator] = filter[:logical_operator] || filter['logical_operator'] || logical_operator
209
- else
210
- new_filter[:conditions] = filter
211
- new_filter[:logical_operator] = logical_operator
212
- end
213
- filter = new_filter
212
+ params.add_filter(filter, logical_operator)
213
+
214
+ # In search: The name is filters and not filter
215
+ params[:filters] = params[:filter]
216
+ params.delete(:filter)
214
217
 
215
218
  resp =
216
219
  @connection.post('_search', params) do |req|
217
- if filter.is_a? Array
218
- req.headers["Content-Type"] = 'application/vnd+shotgun.api3_array+json'
219
- else
220
- req.headers['Content-Type'] = 'application/vnd+shotgun.api3_hash+json'
221
- end
222
- req.body = params.to_h.merge(filters: filter).to_json
223
- pp JSON.parse(req.body)
220
+ req.headers['Content-Type'] =
221
+ if params[:filters].is_a? Array
222
+ 'application/vnd+shotgun.api3_array+json'
223
+ else
224
+ 'application/vnd+shotgun.api3_hash+json'
225
+ end
226
+ req.body = params.to_h.to_json
224
227
  end
225
228
  resp_body = JSON.parse(resp.body)
226
229
 
@@ -228,57 +231,27 @@ module ShotgunApiRuby
228
231
  raise "Error while getting #{type}: #{resp_body['errors']}"
229
232
  end
230
233
 
231
- resp_body["data"].map do |entity|
234
+ resp_body['data'].map do |entity|
232
235
  Entity.new(
233
236
  entity['type'],
234
237
  OpenStruct.new(entity['attributes']),
235
238
  entity['relationships'],
236
239
  entity['id'],
237
- entity['links']
240
+ entity['links'],
238
241
  )
239
242
  end
240
243
  end
241
244
 
242
- private
243
-
244
- def filters_are_simple?(filters)
245
- return false if filters.is_a? Array
245
+ def schema_client
246
+ @schema_client ||= Schema.new(connection, type, @base_url_prefix)
247
+ end
246
248
 
247
- filters.values.all? do |filter_val|
248
- (filter_val.is_a?(Integer) || filter_val.is_a?(String) || filter_val.is_a?(Symbol)) ||
249
- (filter_val.is_a?(Array) && filter_val.all?{ |val|
250
- val.is_a?(String) || val.is_a?(Symbol) || val.is_a?(Integer)
251
- } )
252
- end
249
+ def schema
250
+ schema_client.read
253
251
  end
254
252
 
255
- def translate_complex_to_sg_filters(filters)
256
- # We don't know how to translate anything but hashes
257
- return filters if !filters.is_a?(Hash)
258
-
259
- filters.each.with_object([]) do |item, result|
260
- field, value = item
261
- case value
262
- when String, Symbol, Integer, Float
263
- result << [field, "is", value]
264
- when Hash
265
- value.each do |subfield, subvalue|
266
- sanitized_subfield = "#{field.capitalize}.#{subfield}" unless subfield.to_s.include?(".")
267
- case subvalue
268
- when String, Symbol, Integer, Float
269
- result << ["#{field}.#{sanitized_subfield}", "is", subvalue]
270
- when Array
271
- result << ["#{field}.#{sanitized_subfield}", "in", subvalue]
272
- else
273
- raise "This case is too complex to auto-translate. Please use shotgun query syntax."
274
- end
275
- end
276
- when Array
277
- result << [field, "in", value]
278
- else
279
- raise "This case is too complex to auto-translate. Please use shotgun query syntax."
280
- end
281
- end
253
+ def fields
254
+ schema_client.fields
282
255
  end
283
256
  end
284
257
  end