0

I am using Subprocess call to execute Insert_API from main script.Values to be inserted are stored in a list and I need to pass the list as an argument while subprocess call. It gives me error saying arguments must be strings . If I make str(list) then it works but Insert does not work properly as its passed as string not as list. Tried with passing as string and converting again to list in script but that also does not work properly. Is there any way to pass List as it is ? I have 2 lists to pass everytime. Kindly, Let me know if there is any way to pass 2 lists in subprocess call ? Sample code :

subprocess.check_output(["python","Metadata_Index_EAV_LOAD.py",Dataset1,MetaDataIndex_Table,SchemaFields,FieldValues,r_date])

SchemaField and result are two lists that I need to pass as an argument.

Mikhail Berlyant
  • 165,386
  • 8
  • 154
  • 230
Shikha
  • 321
  • 1
  • 4
  • 19
  • 1
    Since the script you are calling is a Python file, why don't you import it and call the function directly? – Daniel Roseman Sep 08 '17 at 10:57
  • The script for Inserting data into bq table should be independent so that it can be used widely by team. If I import it directly then it will be kind of hardcoding and that needs to be avoided.Required is : pass the table_name,dataset,schemafileds(list),values(values) and get the records inserted. Hope, i am able to explain why avoiding direct import. – Shikha Sep 08 '17 at 11:16
  • There is no variable named `list` in your sample code. What are the types of the other variables? Ultimately, when you use `subprocess`, it's (mostly) the equivalent of invoking the specified program via your shell. If you were running `Metadata_Index_EAV_LOAD.py` directly, how would you do it? What would its arguments look like? Make your call to `subprocess` look like that. – jamesdlin Sep 08 '17 at 11:58
  • SchemaField and result are the list names. When I am running the script directly then not passing anything via command line. Lists were hardcoded in this script for testing . Now i want to automate it by passing the list via main script. The target table is EAV format. and using below code to insert record: Dictionary = dict(zip(SchemaFields,result)) print (Dictionary) bigquery_client = bigquery.Client(project = 'abc') dataset = bigquery_client.dataset(Dataset) table = dataset.table(MetaDataIndex_Table) – Shikha Sep 08 '17 at 12:11
  • complete code in Metadata_Index_EAV_LOAD.py :for i in range(len(SchemaFields)): field = SchemaFields[i] var = '["' + table_uuid + '","' + file_type + '","' + Reporting_Date + '","' + Created + '","' + field + '","' + Dictionary[field] + '","' + Datatype + '"]' #print ("Field is : ") #print (field) #print (var) try: data = json.loads(var) except: else: table.reload() rows = [data] errors = table.insert_data(rows). – Shikha Sep 08 '17 at 12:20
  • cant write complete code as characters restriction here. But please guide me if there is any other simple way to insert data in EAV type table. – Shikha Sep 08 '17 at 12:22
  • `Metadata_Index_EAV_LOAD.py` should be a very thin wrapper around a module that can be reused so that you don't need to use `subprocess` in the other script. – chepner Sep 08 '17 at 12:22

3 Answers3

0

You could set up argparse in your mainScript to handle input arguments using

parser = argparse.ArgumentParser()
parser.add_argument('-sf','--schema_field', action='append', help="List of id's")
args = parser.parse_args()

this way in your command line argument you just add id's to the list by re-using the shortcut.

python -c mainScript.py -sf 1001 -sf 1002 -sf 1003

And this should return args.schema_field as a list inside your mainScript.

Handling complex optional and positional commandline arguments is definitely the job for argparse - so I recommend going over the official docs here : https://docs.python.org/2/howto/argparse.html

MattWBP
  • 376
  • 1
  • 10
0

You probably could find a way of passing the arguments as list such as suggested in this answer but still maybe an easier solution would be to use the json.loads method.

Instead of running list(SchemaFields) for instance try running something like:

import json
schema_list = json.loads(SchemaFields)
values = json.loads(FieldValues)

And you probably will have to keep sending the arguments as strings.

Still, I agree with Roseman's comment, it just seems if you used some base class with all these operations ready to go and just invoked them from your main script this would be much simpler (not sure what you mean by hard-coding values this way as still you could send sys.args input in the main script).

Willian Fuks
  • 11,259
  • 10
  • 50
  • 74
0

Perhaps you wish to pass a list through args, but this shouldn't be done so. Why? Because length of the command line passed to the kernel is limited. Newest Linux distros accept pretty long strings, but still. When you have a lot of stuff that can contain special characters and so on, it sometimes may go terribly wrong. Especially if you will give usage of the receiver script to other people, who can forget to bypass shell and send the data directly to your script. And in this case whole mess can rise.

So, I advise you to use pipes. I.e. in receiver read from sys.stdin, and in sender use Popen().communicate(). Something like this:

# Receiver:
from cPickle import loads
import sys

data = sys.stdin.read() # This will return after '^D' is received.
data = loads(data)
print "I received:"
for x in data:
    print x
    print "\t", data[x]

# Sender:
from cPickle import dumps
from subprocess import Popen, PIPE

data = {"list1": [1, 2, 3, 4],
    "list2": [3, 4, 5, 6]}
cmd = ["python", "receiver.py"]
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
result, error = p.communicate(dumps(data))
if error:
    raise Exception(error)
print "Receiver sent:"
print result

Now, if other people need not to use Python to send data to your script, then do not use pickling but json as suggested in another answer. Or you can even use literal_eval():

# In receiver:
from ast import literal_eval
import sys

data = sys.stdin.read()
data = literal_eval(data)
#...

# And in sender:
# ...
result, error = p.communicate(repr(data))
#...

literal_eval() is a secure version of eval() that will evaluate only built-in data types, not expressions and other objects.

If you still insist on using args, then you should use something like:

list1 = [1, 2, 3, 4]

def convert (l):
    return ",".join((str(x) for x in l))

def get (s):
    return s.split(",")

But then you have to make sure that your separator isn't appearing anywhere else in the string or make an escaping mechanism. Also if you would not be sending only alpha-numeric characters you would have to encode the stuff using URL-safe base64 to avoid possible clashes with OS and/or shell.

Dalen
  • 4,128
  • 1
  • 17
  • 35
  • Thank you. It is quite more clear to understand.My list contains special chars etc...so it seems it is more perfect way.will try it out and will update how it goes.Thank you so much in advance ! – Shikha Sep 08 '17 at 12:49
  • I used Pipes and it works as per my need. Thanks a lot for a brilliant solution. – Shikha Sep 11 '17 at 10:08