1
\$\begingroup\$

I wrote a script to execute a service on the Forestry TEP platform via its REST API. This service has certain input parameters, some of them are numerical values, other strings or files. The current workflow is:

  1. There is a configuration file that has options for file and literal inputs. Each option is a dictionary, where each key is equal to one parameter of the service I want to execute. Example of such file:

    file_inputs= {"cfg" : "forest_flux/FTEP_service/cfg.ini",
                  "parsawen" : "forest_flux/FTEP_service/parsawen.csv",
                  "weather" : "forestflux/data/fin_01/weather/weather_2004.csv"}
    literal_inputs = {"version" : 3}
    
  2. In the script, I read the cfg and iterate over items in these dictionaries. For each key,value pair, I use exec() to store the value in a class variable of the same name. For file inputs, I first upload the file on the platform and store the file's location on the platform in the variable.

        input_files_dict = json.loads(cfg.get("service_info", "file_inputs"))
        for key, val in input_files_dict.items():
            exec("self.{} = self.post_data('{}')".format(key, val))
    
        literal_inputs_dict = json.loads(cfg.get("service_info", "literal_inputs"))
        for key, val in literal_inputs_dict.items():
            exec("self.{} = '{}'".format(key, val))
    
  3. I request service inputs and I try to match the service parameters with my class variables. I add it to a dictionary of parameters which I then send to the platform when executing the service.

        r2 = requests.get(url = url2, verify=False, allow_redirects=False, cookies=self.session_cookies)
        data_inputs = json.loads(r2.text)['serviceDescriptor']['dataInputs']
        for _input in data_inputs:
            value_to_set = eval("self." + _input["id"].lower())
            job_config["inputs"][_input["id"]] = [value_to_set]
    

This current workflow works well for me but I have strong suspicion that it is a very bad practice. I have read that using exec() and eval() is discouraged as there are security issues. However, I cannot think of a better way to achieve this functionality, i.e. to pair values in configuration with service inputs automatically. I want this script to be generic, i.e. usable for any service on the platform.

Question: How could I do this without using the problematic functions? How could using these functions be problematic in this case?

Please note that I removed a lot of code that is not related to the problem I am asking about (e.g. error handling).

\$\endgroup\$
  • \$\begingroup\$ You could use ast.literal_eval but beware that "It is possible to crash the Python interpreter with a sufficiently large/complex string due to stack depth limitations in Python’s AST compiler." \$\endgroup\$ – Grajdeanu Alex. Jun 25 at 20:26
3
\$\begingroup\$

You can use getattr, setattr and delattr to retrieve, modify and remove attributes respectively. This avoids most potential messes eval has.

input_files_dict = json.loads(cfg.get("service_info", "file_inputs"))
for key, val in input_files_dict.items():
    setattr(self, key, self.post_data(f'{value}'))
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks! So f'{value}') is equal to '{}'.format(value) ? Is one preferred over the other and why? \$\endgroup\$ – Jan Pisl Jun 26 at 17:40
  • 2
    \$\begingroup\$ @JanPisl Yes. f'{value}' is preferred (as it is shorter) than str.format. But some modern programs still use str.format as they need to support versions of Python that f-strings are not available in. \$\endgroup\$ – Peilonrayz Jun 26 at 17:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.