shotgrid_api_ruby 0.1.2
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 +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
|