mortar 0.15.26 → 0.15.27
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/lib/mortar/auth.rb +13 -5
- data/lib/mortar/command/base.rb +20 -0
- data/lib/mortar/command/help.rb +1 -1
- data/lib/mortar/command/jobs.rb +2 -2
- data/lib/mortar/command/local.rb +21 -8
- data/lib/mortar/command/luigi.rb +86 -0
- data/lib/mortar/generators/project_generator.rb +2 -0
- data/lib/mortar/git.rb +30 -0
- data/lib/mortar/local/controller.rb +6 -2
- data/lib/mortar/local/installutil.rb +9 -1
- data/lib/mortar/local/params.rb +68 -0
- data/lib/mortar/local/pig.rb +8 -29
- data/lib/mortar/local/python.rb +21 -2
- data/lib/mortar/templates/project/gitignore +1 -0
- data/lib/mortar/templates/project/luigiscripts/client.cfg.template +33 -0
- data/lib/mortar/templates/project/luigiscripts/luigiscript.py +134 -0
- data/lib/mortar/templates/project/project.manifest +1 -0
- data/lib/mortar/templates/script/runstillson.sh +25 -0
- data/lib/mortar/version.rb +1 -1
- data/spec/mortar/auth_spec.rb +8 -0
- data/spec/mortar/command/generate_spec.rb +1 -0
- data/spec/mortar/command/jobs_spec.rb +13 -13
- data/spec/mortar/command/local_spec.rb +41 -3
- data/spec/mortar/command/luigi_spec.rb +117 -0
- data/spec/mortar/command/projects_spec.rb +5 -0
- data/spec/mortar/git_spec.rb +105 -0
- data/spec/mortar/local/installutil_spec.rb +6 -0
- data/spec/mortar/local/params_spec.rb +101 -0
- data/spec/mortar/plugin_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- metadata +14 -7
data/lib/mortar/auth.rb
CHANGED
@@ -72,16 +72,24 @@ class Mortar::Auth
|
|
72
72
|
@credentials = ask_for_and_save_credentials
|
73
73
|
end
|
74
74
|
|
75
|
-
def user # :nodoc:
|
76
|
-
|
75
|
+
def user(local=false) # :nodoc:
|
76
|
+
if (local && !has_credentials)
|
77
|
+
"notloggedin@user.org"
|
78
|
+
else
|
79
|
+
get_credentials[0]
|
80
|
+
end
|
77
81
|
end
|
78
82
|
|
79
|
-
def password # :nodoc:
|
80
|
-
|
83
|
+
def password(local=false) # :nodoc:
|
84
|
+
if (local && !has_credentials)
|
85
|
+
"notloggedin"
|
86
|
+
else
|
87
|
+
get_credentials[1]
|
88
|
+
end
|
81
89
|
end
|
82
90
|
|
83
91
|
def user_s3_safe(local = false)
|
84
|
-
user_email = (local
|
92
|
+
user_email = user(local)
|
85
93
|
return user_email.gsub(/[^0-9a-zA-Z]/i, '-')
|
86
94
|
end
|
87
95
|
|
data/lib/mortar/command/base.rb
CHANGED
@@ -104,6 +104,26 @@ class Mortar::Command::Base
|
|
104
104
|
param_list
|
105
105
|
end
|
106
106
|
|
107
|
+
def luigi_parameters
|
108
|
+
parameters = []
|
109
|
+
invalid_arguments.each_slice(2) do |arg_pair|
|
110
|
+
name_with_dashes = arg_pair[0]
|
111
|
+
unless (name_with_dashes.start_with? "--") && (name_with_dashes.length > 2)
|
112
|
+
error("Luigi parameter #{name_with_dashes} must begin with --")
|
113
|
+
end
|
114
|
+
|
115
|
+
unless arg_pair.length == 2
|
116
|
+
error("No value provided for luigi parameter #{name_with_dashes}")
|
117
|
+
end
|
118
|
+
|
119
|
+
name = name_with_dashes[2..name_with_dashes.length-1]
|
120
|
+
value = arg_pair[1]
|
121
|
+
parameters << {"name" => name, "value" => value}
|
122
|
+
end
|
123
|
+
|
124
|
+
parameters
|
125
|
+
end
|
126
|
+
|
107
127
|
def pig_parameters
|
108
128
|
paramfile_params = {}
|
109
129
|
if options[:param_file]
|
data/lib/mortar/command/help.rb
CHANGED
@@ -23,7 +23,7 @@ require "mortar/command/base"
|
|
23
23
|
#
|
24
24
|
class Mortar::Command::Help < Mortar::Command::Base
|
25
25
|
|
26
|
-
PRIMARY_NAMESPACES = %w( auth clusters generate
|
26
|
+
PRIMARY_NAMESPACES = %w( auth config clusters generate jobs local luigi projects )
|
27
27
|
|
28
28
|
# help [COMMAND]
|
29
29
|
#
|
data/lib/mortar/command/jobs.rb
CHANGED
@@ -164,7 +164,7 @@ class Mortar::Command::Jobs < Mortar::Command::Base
|
|
164
164
|
cluster_type = CLUSTER_TYPE__PERMANENT
|
165
165
|
end
|
166
166
|
use_spot_instances = options[:spot] || false
|
167
|
-
api.
|
167
|
+
api.post_pig_job_new_cluster(project_name, script_name, git_ref, cluster_size,
|
168
168
|
:pig_version => pig_version.version,
|
169
169
|
:project_script_path => script.rel_path,
|
170
170
|
:parameters => pig_parameters,
|
@@ -174,7 +174,7 @@ class Mortar::Command::Jobs < Mortar::Command::Base
|
|
174
174
|
:use_spot_instances => use_spot_instances).body
|
175
175
|
else
|
176
176
|
cluster_id = options[:clusterid]
|
177
|
-
api.
|
177
|
+
api.post_pig_job_existing_cluster(project_name, script_name, git_ref, cluster_id,
|
178
178
|
:pig_version => pig_version.version,
|
179
179
|
:project_script_path => script.rel_path,
|
180
180
|
:parameters => pig_parameters,
|
data/lib/mortar/command/local.rb
CHANGED
@@ -225,12 +225,12 @@ class Mortar::Command::Local < Mortar::Command::Base
|
|
225
225
|
|
226
226
|
# local:luigi SCRIPT
|
227
227
|
#
|
228
|
-
# Run a luigi
|
228
|
+
# Run a luigi pipeline script on your local machine in local scheduler mode.
|
229
229
|
# Any additional command line arguments will be passed directly to the luigi script.
|
230
230
|
#
|
231
|
-
# -p, --parameter NAME=VALUE # Set a pig parameter value in your script.
|
232
|
-
# -f, --param-file PARAMFILE # Load pig parameter values from a file.
|
233
231
|
# --project-root PROJECTDIR # The root directory of the project if not the CWD
|
232
|
+
# -p, --parameter NAME=VALUE # [deprecated] Instead, pass luigi parameters directly as options (see below)
|
233
|
+
# -f, --param-file PARAMFILE # [deprecated] Instead, pass luigi parameters directly as options (see below)
|
234
234
|
#
|
235
235
|
#Examples:
|
236
236
|
#
|
@@ -241,8 +241,7 @@ class Mortar::Command::Local < Mortar::Command::Base
|
|
241
241
|
unless script_name
|
242
242
|
error("Usage: mortar local:luigi SCRIPT\nMust specify SCRIPT.")
|
243
243
|
end
|
244
|
-
|
245
|
-
|
244
|
+
|
246
245
|
# cd into the project root
|
247
246
|
project_root = options[:project_root] ||= Dir.getwd
|
248
247
|
unless File.directory?(project_root)
|
@@ -257,10 +256,24 @@ class Mortar::Command::Local < Mortar::Command::Base
|
|
257
256
|
git_ref = sync_code_with_cloud()
|
258
257
|
ENV['MORTAR_LUIGI_GIT_REF'] = git_ref
|
259
258
|
|
259
|
+
# pick up standard luigi-style params provided by the user
|
260
|
+
luigi_cli_parameters = luigi_parameters()
|
261
|
+
|
262
|
+
# pick up old pig-style parameters (included for backwards compatibility)
|
263
|
+
pig_style_parameters = pig_parameters()
|
264
|
+
if pig_style_parameters.length > 0
|
265
|
+
warn "[DEPRECATION] Passing luigi parameters with -p is deprecated. Please pass them directly (e.g. --myparam myvalue)"
|
266
|
+
end
|
267
|
+
|
268
|
+
luigi_cli_parameters.concat(pig_style_parameters)
|
269
|
+
cli_parameters = \
|
270
|
+
luigi_cli_parameters.sort_by { |p| p['name'] }.map { |arg| ["--#{arg['name']}", "#{arg['value']}"] }.flatten
|
271
|
+
|
272
|
+
# get project configuration parameters
|
273
|
+
project_config_params = config_parameters()
|
274
|
+
|
260
275
|
ctrl = Mortar::Local::Controller.new
|
261
|
-
|
262
|
-
luigi_params = luigi_params.map { |arg| ["--#{arg['name']}", "#{arg['value']}"] }.flatten
|
263
|
-
ctrl.run_luigi(pig_version, script, luigi_params)
|
276
|
+
ctrl.run_luigi(pig_version, script, cli_parameters, project_config_params)
|
264
277
|
end
|
265
278
|
|
266
279
|
# local:sqoop_table dbtype database-name table s3-destination
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014 Mortar Data Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require "mortar/command/base"
|
18
|
+
require "time"
|
19
|
+
|
20
|
+
# run luigi pipeline jobs
|
21
|
+
#
|
22
|
+
class Mortar::Command::Luigi < Mortar::Command::Base
|
23
|
+
|
24
|
+
include Mortar::Git
|
25
|
+
|
26
|
+
# luigi SCRIPT
|
27
|
+
#
|
28
|
+
# Run a luigi pipeline.
|
29
|
+
#
|
30
|
+
# -P, --project PROJECTNAME # Use a project that is not checked out in the current directory. Runs code from project's master branch in GitHub rather than snapshotting local code.
|
31
|
+
# -B, --branch BRANCHNAME # Used with --project to specify a non-master branch
|
32
|
+
#
|
33
|
+
# Examples:
|
34
|
+
#
|
35
|
+
# Run the nightly_rollup luigiscript:
|
36
|
+
# $ mortar luigi luigiscripts/nightly_rollup.py
|
37
|
+
#
|
38
|
+
# Run the nightly_rollup luigiscript with two parameters:
|
39
|
+
# $ mortar luigi luigiscripts/nightly_rollup.py --data-date 2012-02-01 --my-param myval
|
40
|
+
#
|
41
|
+
def index
|
42
|
+
script_name = shift_argument
|
43
|
+
unless script_name
|
44
|
+
error("Usage: mortar luigi SCRIPT\nMust specify SCRIPT.")
|
45
|
+
end
|
46
|
+
|
47
|
+
if options[:project]
|
48
|
+
project_name = options[:project]
|
49
|
+
if File.extname(script_name) == ".py"
|
50
|
+
script_name = File.basename(script_name, ".*")
|
51
|
+
end
|
52
|
+
else
|
53
|
+
project_name = project.name
|
54
|
+
script = validate_luigiscript!(script_name)
|
55
|
+
script_name = script.name
|
56
|
+
end
|
57
|
+
|
58
|
+
parameters = luigi_parameters()
|
59
|
+
|
60
|
+
if options[:project]
|
61
|
+
if options[:branch]
|
62
|
+
git_ref = options[:branch]
|
63
|
+
else
|
64
|
+
git_ref = "master"
|
65
|
+
end
|
66
|
+
else
|
67
|
+
git_ref = sync_code_with_cloud()
|
68
|
+
end
|
69
|
+
|
70
|
+
# post job to API
|
71
|
+
response = action("Requesting job execution") do
|
72
|
+
api.post_luigi_job(project_name, script_name, git_ref,
|
73
|
+
:project_script_path => script.rel_path,
|
74
|
+
:parameters => parameters).body
|
75
|
+
end
|
76
|
+
|
77
|
+
display("job_id: #{response['job_id']}")
|
78
|
+
display
|
79
|
+
display("Job status can be viewed on the web at:\n\n #{response['web_job_url']}")
|
80
|
+
display
|
81
|
+
|
82
|
+
response['job_id']
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_command "luigi:run", "luigi"
|
86
|
+
end
|
data/lib/mortar/git.rb
CHANGED
@@ -160,6 +160,19 @@ module Mortar
|
|
160
160
|
manifest_pathlist
|
161
161
|
end
|
162
162
|
|
163
|
+
def add_entry_to_mortar_project_manifest(path, entry)
|
164
|
+
contents = File.open(path, "r") do |manifest|
|
165
|
+
manifest.read.strip
|
166
|
+
end
|
167
|
+
|
168
|
+
if contents && (! contents.include? entry)
|
169
|
+
new_contents = "#{contents}\n#{entry}\n"
|
170
|
+
File.open(path, "w") do |manifest|
|
171
|
+
manifest.write new_contents
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
163
176
|
def add_newline_to_file(path)
|
164
177
|
File.open(path, "r+") do |manifest|
|
165
178
|
contents = manifest.read()
|
@@ -189,12 +202,25 @@ module Mortar
|
|
189
202
|
#
|
190
203
|
def ensure_valid_mortar_project_manifest()
|
191
204
|
if File.exists? project_manifest_name
|
205
|
+
ensure_luigiscripts_in_project_manifest()
|
192
206
|
add_newline_to_file(project_manifest_name)
|
193
207
|
else
|
194
208
|
create_mortar_project_manifest('.')
|
195
209
|
end
|
196
210
|
end
|
197
211
|
|
212
|
+
#
|
213
|
+
# Ensure that the luigiscripts directory,
|
214
|
+
# which was added after some project manifests were
|
215
|
+
# created, is in the manifest (if luigiscripts exists).
|
216
|
+
#
|
217
|
+
def ensure_luigiscripts_in_project_manifest
|
218
|
+
luigiscripts_path = "luigiscripts"
|
219
|
+
if File.directory? luigiscripts_path
|
220
|
+
add_entry_to_mortar_project_manifest(project_manifest_name, luigiscripts_path)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
198
224
|
#
|
199
225
|
# Create a project manifest file
|
200
226
|
#
|
@@ -207,6 +233,10 @@ module Mortar
|
|
207
233
|
if File.directory? "#{path}/lib"
|
208
234
|
manifest.puts "lib"
|
209
235
|
end
|
236
|
+
|
237
|
+
if File.directory? "#{path}/luigiscripts"
|
238
|
+
manifest.puts "luigiscripts"
|
239
|
+
end
|
210
240
|
end
|
211
241
|
end
|
212
242
|
|
@@ -230,10 +230,14 @@ README
|
|
230
230
|
pig.launch_repl(pig_version, pig_parameters)
|
231
231
|
end
|
232
232
|
|
233
|
-
def run_luigi(pig_version, luigi_script,
|
233
|
+
def run_luigi(pig_version, luigi_script, luigi_script_parameters, project_config_parameters)
|
234
|
+
require_aws_keys
|
234
235
|
install_and_configure(pig_version, 'luigi')
|
235
236
|
py = Mortar::Local::Python.new()
|
236
|
-
py.
|
237
|
+
unless py.run_stillson_luigi_client_cfg_expansion(luigi_script, project_config_parameters)
|
238
|
+
error("Unable to expand your configuration template [luigiscripts/client.cfg.template] to [luigiscripts/client.cfg]")
|
239
|
+
end
|
240
|
+
py.run_luigi_script(luigi_script, luigi_script_parameters)
|
237
241
|
end
|
238
242
|
|
239
243
|
def sqoop_export_table(pig_version, connstr, dbtable, s3dest, options)
|
@@ -198,7 +198,12 @@ module Mortar
|
|
198
198
|
|
199
199
|
def url_date(url, command=nil)
|
200
200
|
result = head_resource(url, command)
|
201
|
-
|
201
|
+
last_modified = result.get_header('Last-Modified')
|
202
|
+
if last_modified
|
203
|
+
http_date_to_epoch(last_modified)
|
204
|
+
else
|
205
|
+
nil
|
206
|
+
end
|
202
207
|
end
|
203
208
|
|
204
209
|
# Given a subdirectory where we have installed some software
|
@@ -211,6 +216,9 @@ module Mortar
|
|
211
216
|
return true
|
212
217
|
end
|
213
218
|
remote_archive_date = url_date(url, command)
|
219
|
+
if not remote_archive_date
|
220
|
+
return false
|
221
|
+
end
|
214
222
|
return existing_install_date < remote_archive_date
|
215
223
|
end
|
216
224
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014 Mortar Data Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'set'
|
18
|
+
require 'mortar/auth'
|
19
|
+
|
20
|
+
module Mortar
|
21
|
+
module Local
|
22
|
+
module Params
|
23
|
+
|
24
|
+
# Job parameters that are supplied automatically from Mortar when
|
25
|
+
# running on the server side. We duplicate these here.
|
26
|
+
def automatic_parameters()
|
27
|
+
params = {}
|
28
|
+
|
29
|
+
params['MORTAR_EMAIL'] = Mortar::Auth.user(true)
|
30
|
+
params['MORTAR_API_KEY'] = Mortar::Auth.password(true)
|
31
|
+
|
32
|
+
if ENV['MORTAR_EMAIL_S3_ESCAPED']
|
33
|
+
params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
|
34
|
+
else
|
35
|
+
params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
if ENV['MORTAR_PROJECT_ROOT']
|
39
|
+
params['MORTAR_PROJECT_ROOT'] = ENV['MORTAR_PROJECT_ROOT']
|
40
|
+
else
|
41
|
+
params['MORTAR_PROJECT_ROOT'] = project_root
|
42
|
+
ENV['MORTAR_PROJECT_ROOT'] = params['MORTAR_PROJECT_ROOT']
|
43
|
+
end
|
44
|
+
|
45
|
+
params['AWS_ACCESS_KEY'] = ENV['AWS_ACCESS_KEY']
|
46
|
+
params['AWS_ACCESS_KEY_ID'] = ENV['AWS_ACCESS_KEY']
|
47
|
+
params['aws_access_key_id'] = ENV['AWS_ACCESS_KEY']
|
48
|
+
|
49
|
+
params['AWS_SECRET_KEY'] = ENV['AWS_SECRET_KEY']
|
50
|
+
params['AWS_SECRET_ACCESS_KEY'] = ENV['AWS_SECRET_KEY']
|
51
|
+
params['aws_secret_access_key'] = ENV['AWS_SECRET_KEY']
|
52
|
+
|
53
|
+
param_list = params.map do |k,v|
|
54
|
+
{"name" => k, "value" => v}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Merge two lists of parameters, removing dupes.
|
59
|
+
# Parameters in param_list_1 override those in param_list_2
|
60
|
+
def merge_parameters(param_list_0, param_list_1)
|
61
|
+
param_list_1_keys = Set.new(param_list_1.map{|item| item["name"]})
|
62
|
+
merged = param_list_1.clone
|
63
|
+
merged.concat(param_list_0.select{|item| (! param_list_1_keys.include? item["name"]) })
|
64
|
+
merged
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/mortar/local/pig.rb
CHANGED
@@ -18,9 +18,11 @@ require "erb"
|
|
18
18
|
require 'tempfile'
|
19
19
|
require "mortar/helpers"
|
20
20
|
require "mortar/local/installutil"
|
21
|
+
require "mortar/local/params"
|
21
22
|
|
22
23
|
class Mortar::Local::Pig
|
23
24
|
include Mortar::Local::InstallUtil
|
25
|
+
include Mortar::Local::Params
|
24
26
|
|
25
27
|
PIG_LOG_FORMAT = "humanreadable"
|
26
28
|
LIB_TGZ_NAME = "lib-common.tar.gz"
|
@@ -399,39 +401,11 @@ class Mortar::Local::Pig
|
|
399
401
|
return opts
|
400
402
|
end
|
401
403
|
|
402
|
-
# Pig Paramenters that are supplied directly from Mortar when
|
403
|
-
# running on the server side. We duplicate these here.
|
404
|
-
def automatic_pig_parameters
|
405
|
-
params = {}
|
406
|
-
|
407
|
-
if ENV['MORTAR_EMAIL_S3_ESCAPED']
|
408
|
-
params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
|
409
|
-
else
|
410
|
-
params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe(true)
|
411
|
-
end
|
412
|
-
|
413
|
-
if ENV['MORTAR_PROJECT_ROOT']
|
414
|
-
params['MORTAR_PROJECT_ROOT'] = ENV['MORTAR_PROJECT_ROOT']
|
415
|
-
else
|
416
|
-
params['MORTAR_PROJECT_ROOT'] = project_root
|
417
|
-
ENV['MORTAR_PROJECT_ROOT'] = params['MORTAR_PROJECT_ROOT']
|
418
|
-
end
|
419
|
-
|
420
|
-
|
421
|
-
# Coerce into the same format as pig parameters that were
|
422
|
-
# passed in via the command line or a parameter file
|
423
|
-
param_list = []
|
424
|
-
params.each{ |k,v|
|
425
|
-
param_list.push({"name" => k, "value" => v})
|
426
|
-
}
|
427
|
-
return param_list
|
428
|
-
end
|
429
|
-
|
430
404
|
# Given a set of user specified pig parameters, combine with the
|
431
405
|
# automatic mortar parameters and write out to a tempfile, returning
|
432
406
|
# it's path so it may be referenced later in the process
|
433
407
|
def make_pig_param_file(pig_parameters)
|
434
|
-
mortar_pig_params =
|
408
|
+
mortar_pig_params = automatic_parameters()
|
435
409
|
all_parameters = mortar_pig_params.concat(pig_parameters)
|
436
410
|
param_file = Tempfile.new("mortar-pig-parameters")
|
437
411
|
all_parameters.each { |p|
|
@@ -447,4 +421,9 @@ class Mortar::Local::Pig
|
|
447
421
|
param_file.path
|
448
422
|
end
|
449
423
|
|
424
|
+
def automatic_pig_parameters
|
425
|
+
warn "[DEPRECATION] Please call automatic_parameters instead"
|
426
|
+
automatic_parameters
|
427
|
+
end
|
428
|
+
|
450
429
|
end
|