shotgrid_api_ruby 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test_and_publish.yml +58 -0
- data/.github/workflows/test_only.yml +37 -0
- data/.github/workflows/verify_version_change.yml +21 -0
- data/.gitignore +14 -0
- data/.overcommit.yml +21 -0
- data/.prettierrc.js +4 -0
- data/.rspec +3 -0
- data/.rubocop-http---relaxed-ruby-style-rubocop-yml +153 -0
- data/.rubocop.yml +33 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile +6 -0
- data/Guardfile +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +470 -0
- data/Rakefile +3 -0
- data/bin/console +48 -0
- data/bin/prettirun +1 -0
- data/bin/ruborun +1 -0
- data/bin/setup +7 -0
- data/lib/shotgrid_api_ruby.rb +19 -0
- data/lib/shotgrid_api_ruby/auth.rb +124 -0
- data/lib/shotgrid_api_ruby/client.rb +79 -0
- data/lib/shotgrid_api_ruby/entities.rb +281 -0
- data/lib/shotgrid_api_ruby/entities/params.rb +182 -0
- data/lib/shotgrid_api_ruby/entities/schema.rb +50 -0
- data/lib/shotgrid_api_ruby/entities/summarize.rb +64 -0
- data/lib/shotgrid_api_ruby/entity.rb +20 -0
- data/lib/shotgrid_api_ruby/preferences.rb +24 -0
- data/lib/shotgrid_api_ruby/server_info.rb +23 -0
- data/lib/shotgrid_api_ruby/version.rb +5 -0
- data/package.json +12 -0
- data/shotgrid_api_ruby.gemspec +59 -0
- data/yarn.lock +15 -0
- metadata +390 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShotgridApiRuby
|
4
|
+
class Entities
|
5
|
+
class Params < Hash
|
6
|
+
class TooComplexFiltersError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_sort(sort)
|
10
|
+
return unless sort
|
11
|
+
|
12
|
+
self[:sort] =
|
13
|
+
if sort.is_a?(Hash)
|
14
|
+
sort.map do |field, direction|
|
15
|
+
"#{direction.to_s.start_with?('desc') ? '-' : ''}#{field}"
|
16
|
+
end.join(',')
|
17
|
+
else
|
18
|
+
[sort].flatten.join(',')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_page(page, page_size)
|
23
|
+
return unless page || page_size
|
24
|
+
|
25
|
+
page = page.to_i if page
|
26
|
+
page_size = page_size.to_i if page_size
|
27
|
+
|
28
|
+
page = 1 if page && page < 1
|
29
|
+
self[:page] = { size: page_size || 20, number: page || 1 }
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_fields(fields)
|
33
|
+
self[:fields] =
|
34
|
+
fields && !fields.empty? ? [fields].flatten.join(',') : '*'
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_options(return_only, include_archived_projects)
|
38
|
+
return if return_only.nil? && include_archived_projects.nil?
|
39
|
+
|
40
|
+
self[:options] = {
|
41
|
+
return_only: return_only ? 'retired' : 'active',
|
42
|
+
include_archived_projects: !!include_archived_projects,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_filter(filters, logical_operator = 'and')
|
47
|
+
return unless filters
|
48
|
+
|
49
|
+
self[:filter] =
|
50
|
+
if (self.class.filters_are_simple?(filters))
|
51
|
+
translate_simple_filters_to_sg(filters)
|
52
|
+
elsif filters.is_a? Hash
|
53
|
+
{
|
54
|
+
conditions:
|
55
|
+
filters[:conditions] || filters['conditions'] ||
|
56
|
+
translate_complex_filters_to_sg(filters),
|
57
|
+
logical_operator:
|
58
|
+
filters[:logical_operator] || filters['logical_operator'] ||
|
59
|
+
logical_operator,
|
60
|
+
}
|
61
|
+
else
|
62
|
+
{ conditions: filters, logical_operator: logical_operator }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_grouping(grouping)
|
67
|
+
return unless grouping
|
68
|
+
|
69
|
+
if grouping.is_a? Array
|
70
|
+
self[:grouping] = grouping
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
self[:grouping] =
|
75
|
+
grouping
|
76
|
+
.each
|
77
|
+
.with_object([]) do |(key, options), result|
|
78
|
+
if options.is_a? Hash
|
79
|
+
result << {
|
80
|
+
field: key.to_s,
|
81
|
+
type:
|
82
|
+
options[:type]&.to_s || options['type']&.to_s || 'exact',
|
83
|
+
direction:
|
84
|
+
options[:direction]&.to_s || options['direction']&.to_s ||
|
85
|
+
'asc',
|
86
|
+
}
|
87
|
+
else
|
88
|
+
result << {
|
89
|
+
field: key.to_s,
|
90
|
+
type: 'exact',
|
91
|
+
direction: options.to_s,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_summary_fields(summary_fields)
|
98
|
+
return unless summary_fields
|
99
|
+
|
100
|
+
if summary_fields.is_a? Array
|
101
|
+
self[:summary_fields] = summary_fields
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
if summary_fields.is_a? Hash
|
106
|
+
self[:summary_fields] =
|
107
|
+
summary_fields.map { |k, v| { field: k.to_s, type: v.to_s } }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.filters_are_simple?(filters)
|
112
|
+
return false if filters.is_a? Array
|
113
|
+
|
114
|
+
if filters.is_a?(Hash) &&
|
115
|
+
(filters[:conditions] || filters['conditions'])
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
|
119
|
+
filters.values.all? do |filter_val|
|
120
|
+
(
|
121
|
+
filter_val.is_a?(Integer) || filter_val.is_a?(String) ||
|
122
|
+
filter_val.is_a?(Symbol)
|
123
|
+
) ||
|
124
|
+
(
|
125
|
+
filter_val.is_a?(Array) && filter_val.all? do |val|
|
126
|
+
val.is_a?(String) || val.is_a?(Symbol) || val.is_a?(Integer)
|
127
|
+
end
|
128
|
+
)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def translate_simple_filters_to_sg(filters)
|
135
|
+
filters.map do |field, value|
|
136
|
+
[
|
137
|
+
field.to_s,
|
138
|
+
value.is_a?(Array) ? value.map(&:to_s).join(',') : value.to_s,
|
139
|
+
]
|
140
|
+
end.to_h
|
141
|
+
end
|
142
|
+
|
143
|
+
def translate_complex_filters_to_sg(filters)
|
144
|
+
# We don't know how to translate anything but hashes
|
145
|
+
return filters if !filters.is_a?(Hash)
|
146
|
+
|
147
|
+
filters
|
148
|
+
.each
|
149
|
+
.with_object([]) do |item, result|
|
150
|
+
field, value = item
|
151
|
+
case value
|
152
|
+
when String, Symbol, Integer, Float
|
153
|
+
result << [field.to_s, 'is', value]
|
154
|
+
when Hash
|
155
|
+
value.each do |subfield, subvalue|
|
156
|
+
sanitized_subfield =
|
157
|
+
if !subfield.to_s.include?('.')
|
158
|
+
"#{field.capitalize}.#{subfield}"
|
159
|
+
else
|
160
|
+
subfield
|
161
|
+
end
|
162
|
+
case subvalue
|
163
|
+
when String, Symbol, Integer, Float
|
164
|
+
result << ["#{field}.#{sanitized_subfield}", 'is', subvalue]
|
165
|
+
when Array
|
166
|
+
result << ["#{field}.#{sanitized_subfield}", 'in', subvalue]
|
167
|
+
else
|
168
|
+
raise TooComplexFiltersError,
|
169
|
+
'This case is too complex to auto-translate. Please use shotgrid query syntax.'
|
170
|
+
end
|
171
|
+
end
|
172
|
+
when Array
|
173
|
+
result << [field.to_s, 'in', value]
|
174
|
+
else
|
175
|
+
raise TooComplexFiltersError,
|
176
|
+
'This case is too complex to auto-translate. Please use shotgrid query syntax.'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShotgridApiRuby
|
4
|
+
class Entities
|
5
|
+
class Schema
|
6
|
+
def initialize(connection, type, base_url_prefix)
|
7
|
+
@connection = connection.dup
|
8
|
+
@type = type
|
9
|
+
@connection.url_prefix = "#{base_url_prefix}/schema/#{type}"
|
10
|
+
end
|
11
|
+
attr_reader :type, :connection
|
12
|
+
|
13
|
+
def read
|
14
|
+
resp = @connection.get('')
|
15
|
+
|
16
|
+
if resp.status >= 300
|
17
|
+
raise "Error while read schema for #{type}: #{resp.body}"
|
18
|
+
end
|
19
|
+
|
20
|
+
resp_body = JSON.parse(resp.body)
|
21
|
+
|
22
|
+
OpenStruct.new(resp_body['data'].transform_values { |v| v['value'] })
|
23
|
+
end
|
24
|
+
|
25
|
+
def fields
|
26
|
+
resp = @connection.get('fields')
|
27
|
+
resp_body = JSON.parse(resp.body)
|
28
|
+
|
29
|
+
if resp.status >= 300
|
30
|
+
raise "Error while read schema fields for #{type}: #{resp.body}"
|
31
|
+
end
|
32
|
+
|
33
|
+
OpenStruct.new(
|
34
|
+
resp_body['data'].transform_values do |value|
|
35
|
+
OpenStruct.new(
|
36
|
+
value.transform_values { |attribute| attribute['value'] }.merge(
|
37
|
+
properties:
|
38
|
+
OpenStruct.new(
|
39
|
+
value['properties'].transform_values do |prop|
|
40
|
+
prop['value']
|
41
|
+
end,
|
42
|
+
),
|
43
|
+
),
|
44
|
+
)
|
45
|
+
end,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ShotgridApiRuby
|
2
|
+
class Entities
|
3
|
+
class Summarize
|
4
|
+
Summary = Struct.new(:summaries, :groups)
|
5
|
+
|
6
|
+
def initialize(connection, type, base_url_prefix)
|
7
|
+
@connection = connection.dup
|
8
|
+
@type = type
|
9
|
+
@connection.url_prefix = "#{base_url_prefix}/entity/#{type}/_summarize"
|
10
|
+
end
|
11
|
+
attr_reader :type, :connection
|
12
|
+
|
13
|
+
def count(filter: nil, logical_operator: 'and')
|
14
|
+
result =
|
15
|
+
summarize(
|
16
|
+
filter: filter,
|
17
|
+
logical_operator: logical_operator,
|
18
|
+
summary_fields: [{ type: :record_count, field: 'id' }],
|
19
|
+
)
|
20
|
+
result.summaries&.[]('id') || 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def summarize(
|
24
|
+
filter: nil,
|
25
|
+
grouping: nil,
|
26
|
+
summary_fields: nil,
|
27
|
+
logical_operator: 'and',
|
28
|
+
include_archived_projects: nil
|
29
|
+
)
|
30
|
+
params = Params.new
|
31
|
+
|
32
|
+
params.add_filter(filter, logical_operator)
|
33
|
+
|
34
|
+
params[:filters] = params[:filter] if params[:filter]
|
35
|
+
params.delete(:filter)
|
36
|
+
|
37
|
+
params.add_grouping(grouping)
|
38
|
+
params.add_summary_fields(summary_fields)
|
39
|
+
params.add_options(nil, include_archived_projects)
|
40
|
+
|
41
|
+
resp =
|
42
|
+
@connection.post('', params) do |req|
|
43
|
+
req.headers['Content-Type'] =
|
44
|
+
if params[:filters].is_a? Array
|
45
|
+
'application/vnd+shotgun.api3_array+json'
|
46
|
+
else
|
47
|
+
'application/vnd+shotgun.api3_hash+json'
|
48
|
+
end
|
49
|
+
req.body = params.to_h.to_json
|
50
|
+
end
|
51
|
+
resp_body = JSON.parse(resp.body)
|
52
|
+
|
53
|
+
if resp.status >= 300
|
54
|
+
raise "Error while getting summarize for #{type}: #{resp_body['errors']}"
|
55
|
+
end
|
56
|
+
|
57
|
+
Summary.new(
|
58
|
+
resp_body['data']['summaries'],
|
59
|
+
resp_body['data']&.[]('groups'),
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShotgridApiRuby
|
4
|
+
# Represent each entity returned by Shotgrid
|
5
|
+
Entity =
|
6
|
+
Struct.new(:type, :attributes, :relationships, :id, :links) do
|
7
|
+
def method_missing(name, *args, &block)
|
8
|
+
if attributes.respond_to?(name)
|
9
|
+
define_singleton_method(name) { attributes.public_send(name) }
|
10
|
+
public_send(name)
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(name, _private_methods = false)
|
17
|
+
attributes.respond_to?(name) || super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShotgridApiRuby
|
4
|
+
class Preferences
|
5
|
+
def initialize(connection)
|
6
|
+
@connection = connection.dup
|
7
|
+
@connection.url_prefix = "#{@connection.url_prefix}/preferences"
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :connection
|
11
|
+
|
12
|
+
def all
|
13
|
+
resp = @connection.get
|
14
|
+
resp_body = JSON.parse(resp.body)
|
15
|
+
|
16
|
+
if resp.status >= 300
|
17
|
+
raise "Error while getting server preferences: #{resp_body['errors']}"
|
18
|
+
end
|
19
|
+
|
20
|
+
data = resp_body['data']
|
21
|
+
OpenStruct.new(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShotgridApiRuby
|
4
|
+
class ServerInfo
|
5
|
+
def initialize(connection)
|
6
|
+
@connection = connection
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :connection
|
10
|
+
|
11
|
+
def get
|
12
|
+
resp = @connection.get
|
13
|
+
resp_body = JSON.parse(resp.body)
|
14
|
+
|
15
|
+
if resp.status >= 300
|
16
|
+
raise "Error while getting server infos: #{resp_body['errors']}"
|
17
|
+
end
|
18
|
+
|
19
|
+
data = resp_body['data']
|
20
|
+
OpenStruct.new(data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/package.json
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
{
|
2
|
+
"name": "shotgrid_api_ruby",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "index.js",
|
5
|
+
"repository": "git@github.com:shotgunsoftware/shotgrid_api_ruby.git",
|
6
|
+
"author": "Denis <Zaratan> Pasin <zaratan@hey.com>",
|
7
|
+
"license": "MIT",
|
8
|
+
"devDependencies": {
|
9
|
+
"@prettier/plugin-ruby": "^1.3.0",
|
10
|
+
"prettier": "^2.2.1"
|
11
|
+
}
|
12
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'shotgrid_api_ruby/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'shotgrid_api_ruby'
|
9
|
+
spec.version = ShotgridApiRuby::VERSION
|
10
|
+
spec.authors = ['Denis <Zaratan> Pasin']
|
11
|
+
spec.email = ['denis.pasin@autodesk.com']
|
12
|
+
|
13
|
+
spec.summary = 'Gem to interact easily with Shotgrid REST api.'
|
14
|
+
spec.description =
|
15
|
+
"Gem to facilitate the interaction with Shotgrid's REST API."
|
16
|
+
spec.homepage = 'https://github.com/shotgunsoftware/shotgrid_api_ruby'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
19
|
+
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] =
|
22
|
+
'https://github.com/shotgunsoftware/shotgrid_api_ruby'
|
23
|
+
spec.metadata['changelog_uri'] =
|
24
|
+
'https://github.com/shotgunsoftware/shotgrid_api_ruby/blob/main/CHANGELOG.md'
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files =
|
29
|
+
Dir.chdir(File.expand_path(__dir__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
31
|
+
f.match(%r{^(test|spec|features)/})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
spec.require_paths = ['lib']
|
35
|
+
|
36
|
+
spec.add_dependency 'activesupport'
|
37
|
+
spec.add_dependency 'faraday', '~> 1'
|
38
|
+
spec.add_dependency 'zeitwerk', '~> 2.2'
|
39
|
+
|
40
|
+
spec.add_development_dependency 'bundler'
|
41
|
+
spec.add_development_dependency 'bundler-audit'
|
42
|
+
spec.add_development_dependency 'dotenv'
|
43
|
+
spec.add_development_dependency 'faker', '> 1.8'
|
44
|
+
spec.add_development_dependency 'guard-rspec', '> 4.7'
|
45
|
+
spec.add_development_dependency 'overcommit'
|
46
|
+
spec.add_development_dependency 'prettier'
|
47
|
+
spec.add_development_dependency 'pry'
|
48
|
+
spec.add_development_dependency 'rake'
|
49
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
50
|
+
spec.add_development_dependency 'rspec_in_context', '> 1'
|
51
|
+
spec.add_development_dependency 'rubocop'
|
52
|
+
spec.add_development_dependency 'rubocop-faker'
|
53
|
+
spec.add_development_dependency 'rubocop-performance'
|
54
|
+
spec.add_development_dependency 'simplecov', '> 0.16'
|
55
|
+
spec.add_development_dependency 'solargraph'
|
56
|
+
spec.add_development_dependency 'timecop'
|
57
|
+
spec.add_development_dependency 'vcr'
|
58
|
+
spec.add_development_dependency 'yard'
|
59
|
+
end
|