unimatrix-activist 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a3591cde5aa039445f5a07e48b3fc9818d9aed0a
4
+ data.tar.gz: abd4244b737b74776b35ff975f1e18144188c88e
5
+ SHA512:
6
+ metadata.gz: b31c75f458bd14949e9ef6b434b7931b853c4044686ea6e1a9b33d85e3ff0cfeffd144f86bf33211bceee3b1fd07520a73609dc8a7d80f770b0d921488875e8b
7
+ data.tar.gz: 608b86df4e8fbf05bcbfdb44281c4098c2939e97cb8eff47082c9f9fcba4eaa1d8e958707b5531ed24781ebed0a344e97a50e59eb52cf1e21d7958b584ba7e1e
@@ -0,0 +1,21 @@
1
+ require 'active_support'
2
+ require 'active_support/all'
3
+ require 'fnv'
4
+
5
+ require 'unimatrix/activist/version'
6
+
7
+ require 'unimatrix/activist/configuration'
8
+ require 'unimatrix/activist/operation'
9
+ require 'unimatrix/activist/request'
10
+ require 'unimatrix/activist/response'
11
+ require 'unimatrix/activist/parser'
12
+ require 'unimatrix/activist/serializer'
13
+
14
+ require 'unimatrix/activist/resources/base'
15
+ require 'unimatrix/activist/resources/error'
16
+ require 'unimatrix/activist/resources/activity'
17
+ require 'unimatrix/activist/resources/task'
18
+
19
+ require 'unimatrix/activist/resources/stream_clip_creation_activity'
20
+ require 'unimatrix/activist/resources/stream_clip_creation_task'
21
+ require 'unimatrix/activist/resources/video_ingestion_task'
@@ -0,0 +1,32 @@
1
+ require 'singleton'
2
+
3
+ module Unimatrix::Activist
4
+
5
+ def self.configuration( &block )
6
+ Configuration.instance().instance_eval( &block ) unless block.nil?
7
+ Configuration.instance()
8
+ end
9
+
10
+ class Configuration
11
+ include Singleton
12
+
13
+ def self.field( field_name, options={} )
14
+ class_eval(
15
+ "def #{ field_name }( *arguments ); " +
16
+ "@#{ field_name } = arguments.first unless arguments.empty?; " +
17
+ "@#{ field_name } || " +
18
+ ( options[ :default ].nil? ?
19
+ "nil" :
20
+ ( options[ :default ].is_a?( String ) ?
21
+ "'#{ options[ :default ] }'" :
22
+ "#{ options[ :default ] }" ) ) + ";" +
23
+ "end",
24
+ __FILE__,
25
+ __LINE__
26
+ )
27
+ end
28
+
29
+ field :url, default: ENV[ 'ACTIVIST_URL' ]
30
+ end
31
+
32
+ end
@@ -0,0 +1,143 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Operation
4
+
5
+ def initialize( path, parameters = {} )
6
+ @path = path
7
+ @parameters = ( parameters || {} ).deep_dup
8
+ @key = nil
9
+ end
10
+
11
+ def key
12
+ return @key ||= begin
13
+ result = 0
14
+ query = @parameters.to_param
15
+ if ( @path.present? || @query.present? )
16
+ query = query.split( '&' ).sort.join( '&' )
17
+ addressable = Addressable::URI.new
18
+ addressable.path = @path
19
+ addressable.query = query unless query.blank?
20
+ result = FNV.new.fnv1a_32( addressable.to_s )
21
+ end
22
+ result
23
+ end
24
+ end
25
+
26
+ def where( parameters )
27
+ self.spawn( parameters )
28
+ end
29
+
30
+ def destroy
31
+ result = nil
32
+ Request.new.tap do | request |
33
+ response = request.destroy( @path, @parameters )
34
+ if response.present?
35
+ result = response.resources
36
+ end
37
+ end
38
+ result
39
+ end
40
+
41
+ def limit( _limit )
42
+ self.spawn( count: _limit )
43
+ end
44
+
45
+ def offset( _offset )
46
+ self.spawn( offset: _offset )
47
+ end
48
+
49
+ def include( *arguments )
50
+ self.spawn( :include => self.normalize_include( *arguments ) )
51
+ end
52
+
53
+ def query( &block )
54
+ result = nil
55
+ Request.new.tap do | request |
56
+ request.get( @path, @parameters ).tap do | response |
57
+ result = response.resources
58
+ if block_given?
59
+ case block.arity
60
+ when 0; yield
61
+ when 1; yield result
62
+ when 2; yield result, response
63
+ end
64
+ end
65
+ end
66
+ end
67
+ result
68
+ end
69
+
70
+ def read( &block )
71
+ response = nil
72
+ result = nil
73
+ self.query do | _result, _response |
74
+ result = _result
75
+ response = _response
76
+ end
77
+ if response.success?
78
+ result = result if result.present? && result.is_a?( Array )
79
+ if block_given?
80
+ case block.arity
81
+ when 0; yield
82
+ when 1; yield result
83
+ when 2; yield result, response
84
+ end
85
+ end
86
+ end
87
+ result
88
+ end
89
+
90
+ def write( node, objects, &block )
91
+ result = nil
92
+ Request.new.tap do | request |
93
+ serializer = Unimatrix::Activist::Serializer.new( objects )
94
+ response = request.post( @path, @parameters, serializer.serialize( node ) )
95
+ if response.present?
96
+ result = response.resources
97
+ if block_given?
98
+ case block.arity
99
+ when 0; yield
100
+ when 1; yield result
101
+ when 2; yield result, response
102
+ end
103
+ end
104
+ end
105
+ end
106
+ result
107
+ end
108
+
109
+ protected; def spawn( parameters )
110
+ Operation.new(
111
+ @path,
112
+ @parameters.deep_merge( parameters || {} )
113
+ )
114
+ end
115
+
116
+ protected; def normalize_include( *arguments )
117
+
118
+ includes = {}
119
+ arguments.each do | argument |
120
+ case argument
121
+ when Array
122
+ argument.each do | value |
123
+ includes.deep_merge!( self.normalize_include( value ) )
124
+ end
125
+ when Hash
126
+ argument.each do | key, value |
127
+ if !includes.include?( key ) || includes[ key ] === true
128
+ includes[ key ] = self.normalize_include( value )
129
+ else
130
+ includes[ key ].deep_merge!( self.normalize_include( value ) )
131
+ end
132
+ end
133
+ else
134
+ includes[ argument ] = true
135
+ end
136
+ end
137
+ includes
138
+
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,157 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Parser
4
+
5
+ def initialize( content = {} )
6
+ @content = content
7
+ yield self if block_given?
8
+ end
9
+
10
+ def name
11
+ @content.include?( '$this' ) ?
12
+ @content[ '$this' ][ 'name' ] :
13
+ nil
14
+ end
15
+
16
+ def type_name
17
+ @content.include?( '$this' ) ?
18
+ @content[ '$this' ][ 'type_name' ] :
19
+ nil
20
+ end
21
+
22
+ def key
23
+ 'id'
24
+ end
25
+
26
+ def keys
27
+ @content.include?( '$this' ) ?
28
+ @content[ '$this' ][ 'ids' ] :
29
+ nil
30
+ end
31
+
32
+ def associations
33
+ @content.include?( '$associations' ) ?
34
+ @content[ '$associations' ] :
35
+ nil
36
+ end
37
+
38
+ def resources
39
+ result = nil
40
+
41
+ unless self.name.blank?
42
+ result = self.keys.map do | key |
43
+ self.resource_by( name, key, { 'type_name' => self.type_name } )
44
+ end
45
+ end
46
+ result
47
+ end
48
+
49
+ def parse_resource( name, attributes )
50
+ @resources_mutex ||= Hash.new { | hash, key | hash[ key ] = [] }
51
+ object_key = attributes[ key ]
52
+
53
+ # Lock the resource index for this name/key combination
54
+ # This prevents objects that are associated with
55
+ # themselves from causing a stack overflow
56
+ return nil if @resources_mutex[ name ].include?( object_key )
57
+
58
+ @resources_mutex[ name ].push( object_key )
59
+ resource = nil
60
+
61
+ if attributes.present?
62
+ class_name = name.singularize.camelize
63
+ object_class = Unimatrix::Activist.const_get( class_name ) rescue nil
64
+
65
+ if object_class.present?
66
+ relations = name == self.name ?
67
+ self.parse_associations( attributes ) : []
68
+ resource = object_class.new( attributes, relations )
69
+ end
70
+ @resources_mutex[ name ].delete( object_key )
71
+ end
72
+ resource
73
+ end
74
+
75
+ def resource_by( name, key, options = {} )
76
+
77
+ @resources_index ||= Hash.new { | hash, key | hash[ key ] = {} }
78
+ @resource_index_mutex ||= Hash.new { | hash, key | hash[ key ] = [] }
79
+
80
+ @resources_index[ name ][ key ] ||= begin
81
+
82
+ # lock the resource index for this name/key combination
83
+ # note: this prevents Unimatrix::Activist objects that are associated with
84
+ # themselves from causing a stack overflow
85
+ return nil if @resource_index_mutex[ name ].include?( key )
86
+ @resource_index_mutex[ name ].push( key )
87
+
88
+ result = nil
89
+ resource_attributes = resource_attribute_index[ name ][ key ]
90
+ if resource_attributes.present?
91
+ type_name = resource_attributes[ 'type_name' ]
92
+ klass = nil
93
+ klass = ( Unimatrix::Activist.const_get( type_name.camelize ) rescue nil ) \
94
+ if type_name.present?
95
+ if klass.nil?
96
+ type_name = options[ 'type_name' ]
97
+ klass = ( Unimatrix::Activist.const_get( type_name.camelize ) rescue nil ) \
98
+ if type_name.present?
99
+ end
100
+ if klass.present?
101
+ result = klass.new(
102
+ resource_attributes,
103
+ self.resource_associations_by( name, key )
104
+ )
105
+ end
106
+ end
107
+
108
+ # unlock the resource index for this name/key combination
109
+ @resource_index_mutex[ name ].delete( key )
110
+
111
+ result
112
+
113
+ end
114
+
115
+ end
116
+
117
+ def resource_associations_by( name, key )
118
+ result = Hash.new { | hash, key | hash[ key ] = [] }
119
+ associations = self.associations
120
+ if associations && associations.include?( name )
121
+ association = associations[ name ].detect do | association |
122
+ association[ 'id' ] == key
123
+ end
124
+ if association.present?
125
+ association.each do | key, value |
126
+ unless key == 'id'
127
+ type_name = value[ 'type_name' ]
128
+ result[ key ] = ( value[ 'ids' ] || [] ).map do | associated_id |
129
+ self.resource_by(
130
+ key,
131
+ associated_id,
132
+ { 'type_name' => type_name }
133
+ )
134
+ end
135
+ result[ key ].compact!
136
+ end
137
+ end
138
+ end
139
+ end
140
+ result
141
+ end
142
+
143
+ def resource_attribute_index
144
+ @resource_attribute_index ||= begin
145
+ index = Hash.new { | hash, key | hash[ key ] = {} }
146
+ @content.each do | key, resources_attributes |
147
+ unless key[0] == '$'
148
+ resources_attributes.each do | resource_attributes |
149
+ index[ key ][ resource_attributes[ 'id' ] ] = resource_attributes
150
+ end
151
+ end
152
+ end
153
+ index
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,62 @@
1
+ require 'net/http'
2
+ require 'addressable/uri'
3
+
4
+ module Unimatrix::Activist
5
+
6
+ class Request
7
+
8
+ def initialize( default_parameters = {} )
9
+ uri = URI( Unimatrix::Activist.configuration.url )
10
+ @http = Net::HTTP.new( uri.host, uri.port )
11
+
12
+ @http.use_ssl = ( uri.scheme == 'https' )
13
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
14
+
15
+ @default_parameters = default_parameters.stringify_keys
16
+ end
17
+
18
+ def get( path, parameters = {} )
19
+ response = nil
20
+
21
+ begin
22
+ response = Response.new(
23
+ @http.get( compose_request_path( path, parameters ) )
24
+ )
25
+ rescue Timeout::Error
26
+ response = nil
27
+ end
28
+
29
+ response
30
+ end
31
+
32
+ def post( path, parameters = {}, body = {} )
33
+ response = nil
34
+
35
+ begin
36
+ request = Net::HTTP::Post.new(
37
+ compose_request_path( path, parameters ),
38
+ { 'Content-Type' =>'application/json' }
39
+ )
40
+ request.body = body.to_json
41
+
42
+ response = Response.new( @http.request( request ) )
43
+ rescue Timeout::Error
44
+ response = nil
45
+ end
46
+
47
+ response
48
+ end
49
+
50
+ protected; def compose_request_path( path, parameters = {} )
51
+ parameters = @default_parameters.merge( parameters.stringify_keys )
52
+ addressable = Addressable::URI.new
53
+
54
+ addressable.path = path
55
+ addressable.query = parameters.to_param unless parameters.blank?
56
+
57
+ addressable.to_s
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,20 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Activity < Base
4
+ field :id
5
+ field :type_name
6
+ field :subject_id
7
+ field :subject_type
8
+ field :state
9
+ field :message
10
+ field :properties
11
+ field :completed_at
12
+ field :destroyed_at
13
+ field :created_at
14
+ field :updated_at
15
+ field :execute_at
16
+
17
+ has_many :task
18
+ end
19
+
20
+ end
@@ -0,0 +1,70 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Base
4
+
5
+ class << self
6
+
7
+ def inherited( subclass )
8
+ subclass.fields = {}.merge( self.fields )
9
+ end
10
+
11
+ def field( name, options = {} )
12
+ self.fields[ name.to_sym ] = options.merge( name: name )
13
+
14
+ class_eval(
15
+ "def #{ name }(); " +
16
+ "@#{ name }.is_a?( FalseClass ) ? @#{ name } : (" +
17
+ "@#{ name } || " +
18
+ ( options[ :default ].nil? ?
19
+ "nil" :
20
+ ( options[ :default ].is_a?( String ) ?
21
+ "'#{ options[ :default ] }'" :
22
+ "#{ options[ :default ] }" ) ) + ");" +
23
+ "end;" +
24
+ " " +
25
+ "attr_writer :#{ name };",
26
+ __FILE__,
27
+ __LINE__
28
+ )
29
+
30
+ end
31
+
32
+ def has_one( name, options = {} )
33
+ define_method name do
34
+ associations = self.instance_variable_get( "@_#{ name.to_s.pluralize }" )
35
+ associations.present? ? associations.first : options[ :default ]
36
+ end
37
+ end
38
+
39
+ def has_many( name, options = {} )
40
+ define_method name do
41
+ self.instance_variable_get( "@_#{ name }" ) ||
42
+ options[ :default ] || []
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class_attribute :fields, instance_writer: false
49
+ self.fields = {}
50
+
51
+ field :type_name
52
+ has_many :errors
53
+
54
+ def initialize( attributes={}, associations={} )
55
+ self.type_name = self.class.name.gsub( /Unimatrix::Activist::/, '' ).underscore
56
+
57
+ attributes.each do | key, value |
58
+ send( "#{ key }=", value ) if respond_to?( "#{ key }=" )
59
+ end
60
+
61
+ associations.each do | key, value |
62
+ self.instance_variable_set( "@_#{ key }", value )
63
+ end
64
+
65
+ yield self if block_given?
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,8 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Error < Base
4
+ field :code
5
+ field :message
6
+ end
7
+
8
+ end
@@ -0,0 +1,12 @@
1
+ module Unimatrix::Activist
2
+
3
+ class StreamClipCreationActivity < Activity
4
+ field :in_point
5
+ field :out_point
6
+ field :clip_name
7
+ field :original_stream_id
8
+ field :video_file_url
9
+ field :video_id
10
+ end
11
+
12
+ end
@@ -0,0 +1,11 @@
1
+ module Unimatrix::Activist
2
+
3
+ class StreamClipCreationTask < Task
4
+ field :in_point
5
+ field :clip_name
6
+ field :video_file_url
7
+ field :out_point
8
+ field :original_stream_id
9
+ end
10
+
11
+ end
@@ -0,0 +1,21 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Task < Base
4
+ field :id
5
+ field :type_name
6
+ field :subject_id
7
+ field :activity_id
8
+ field :subject_type
9
+ field :state
10
+ field :message
11
+ field :properties
12
+ field :execute_at
13
+ field :started_at
14
+ field :ended_at
15
+ field :created_at
16
+ field :updated_at
17
+
18
+ has_one :activity
19
+ end
20
+
21
+ end
@@ -0,0 +1,9 @@
1
+ module Unimatrix::Activist
2
+
3
+ class VideoIngestionTask < Task
4
+ field :video_id
5
+ field :clip_name
6
+ field :video_file_url
7
+ end
8
+
9
+ end
@@ -0,0 +1,44 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Response
4
+
5
+ attr_reader :code
6
+ attr_reader :body
7
+ attr_reader :resources
8
+
9
+ def initialize( http_response )
10
+ @success = http_response.is_a?( Net::HTTPOK )
11
+ @code = http_response.code
12
+ @resources = []
13
+ @body = decode_response_body( http_response )
14
+
15
+ if ( @body && @body.respond_to?( :keys ) )
16
+ Parser.new( @body ) do | parser |
17
+ @resources = parser.resources
18
+ @success = !( parser.type_name == 'error' )
19
+ end
20
+ else
21
+ @success = false
22
+ @resources << Error.new(
23
+ message: "#{ @code }: #{ http_response.message }."
24
+ )
25
+ end
26
+ end
27
+
28
+ def success?
29
+ @success
30
+ end
31
+
32
+ protected; def decode_response_body( http_response )
33
+ body = http_response.body
34
+
35
+ if body.present?
36
+ JSON.parse( body ) rescue nil
37
+ else
38
+ nil
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,31 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Serializer
4
+
5
+ def initialize( payload = [], options = {} )
6
+ @payload = [ payload ].flatten
7
+ @options = options
8
+ end
9
+
10
+ def serialize( node, options = {} )
11
+ result = {}
12
+ result[ node ] = @payload.map do | object |
13
+ node_object = {}
14
+ node_object[ :type_name ] = (
15
+ object.respond_to?( :type_name ) ?
16
+ object.type_name :
17
+ object.class.name.gsub( /Unimatrix::Activist/, '' ).underscore
18
+ )
19
+ if object.respond_to?( :fields )
20
+ object.fields.each do | name, options |
21
+ node_object[ name.to_sym ] = object.send( name )
22
+ end
23
+ end
24
+ node_object
25
+ end
26
+ result
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,5 @@
1
+ module Unimatrix
2
+ module Activist
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unimatrix-activist
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Yang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fnv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-nav
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: The unimatrix-activist facilitates making requests to the Unimatrix Activist
84
+ application
85
+ email:
86
+ - matthewyang@sportsrocket.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - lib/unimatrix-activist.rb
92
+ - lib/unimatrix/activist/configuration.rb
93
+ - lib/unimatrix/activist/operation.rb
94
+ - lib/unimatrix/activist/parser.rb
95
+ - lib/unimatrix/activist/request.rb
96
+ - lib/unimatrix/activist/resources/activity.rb
97
+ - lib/unimatrix/activist/resources/base.rb
98
+ - lib/unimatrix/activist/resources/error.rb
99
+ - lib/unimatrix/activist/resources/stream_clip_creation_activity.rb
100
+ - lib/unimatrix/activist/resources/stream_clip_creation_task.rb
101
+ - lib/unimatrix/activist/resources/task.rb
102
+ - lib/unimatrix/activist/resources/video_ingestion_task.rb
103
+ - lib/unimatrix/activist/response.rb
104
+ - lib/unimatrix/activist/serializer.rb
105
+ - lib/unimatrix/activist/version.rb
106
+ homepage: http://sportsrocket.com
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.4.6
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Unimatrix::Activist is used to communicate with Activist.
130
+ test_files: []