I have a BatchJob specification file (batch.spec) something like below:
python = <<EOF
def foo():
return f"foo: I am {self.name}"
def bar():
return f"bar: I am {self.name}"
EOF
<batch>
name p&l_calculator
exec echo %foo()%
</batch>
I am converting this file to a python dict by using https://github.com/etingof/apacheconfig
from apacheconfig import *
with make_loader() as loader:
config = loader.load('batch.spec')
# Print content of python dict
for key, value in config.items():
print(key, value)
# output from print statement above
# python
# def foo():
# return f"foo: I am {self.name}"
#
# def bar():
# return f"bar: I am {self.name}"
# batch {'name': 'p&l_calculator', 'exec': 'echo %foo()%'}
Now I am converting this python dict to a BatchJobSpec object like below:
class BatchJobSpec:
def __init__(self, pythoncode , batch):
self.pythoncode = pythoncode
self.name = batch.get("name")
self.exec = batch.get("exec")
batchjobspec = BatchJobSpec(config.get("python"), config.get("batch"))
If I print batchjobspec fields, then I will get something like below
print(batchjobspec.pythoncode)
print(batchjobspec.name)
print(batchjobspec.exec)
# Output from above print statements
# def foo():
# return f"foo: I am {self.name}"
#
# def bar():
# return f"bar: I am {self.name}"
# p&l_calculator
# echo %foo()%
Problem: I want the value of "batchjobspec.exec" to be interpolated when I try to access it, i.e. it should not be "echo %foo()%" but it should be "echo foo: I am p&l_calculator".
i.e. somehow in the getter of fields, I want to check if there is "% %" syntax. If yes, and if content inside "% %" contains a callable then I want to call that callable and append the value returned from callable to the value of the corresponding field. I guess I will have to make these callables available in the BatchJobSpec dict as well.
Comment: I have foo as well as bar. exec(str) will execute both
In your self.pythoncode
you have definitons of functions like def foo():
. Therefore exec
doesn't execute but do define these functions in the local namespace. To execute these functions as a class methode
, you have to create a attribute
, referencing these local functions, in the class
itself.
In this example, the function names are known beforhand
class BatchJobSpec:
def __init__(self, pythoncode , batch):
self.pythoncode = pythoncode
self.name = batch['name']
self._exec = ['for', 'bar']
self.exec()
To make self
visible in the local namespace, define the locals_
dict.
exec
the string self.pythoncode
results in insert the reference to the functions into locals_
. To use these local references, as a class methode
and make it persistent, use self.__setattr__(...
.
def exec(self):
locals_ = {'self': self}
exec(self.pythoncode, locals_)
for attrib in self._exec:
print('__setattr__({})'.format(attrib))
self.__setattr__(attrib, locals_[attrib])
Usage: Different python format
syntax, as i using python 3.5
python = """
def foo():
return 'foo: I am {name}'.format(name=self.name)
def bar():
return 'bar: I am {name}'.format(name=self.name)
"""
if __name__ == "__main__":
batchjobspec = BatchJobSpec(config.get("python"), config.get("batch"))
print('echo {}'.format(batchjobspec.foo()))
print('echo {}'.format(batchjobspec.bar()))
Output:
__setattr__(foo) __setattr__(bar) echo foo: I am p&l_calculator echo bar: I am p&l_calculator
Question I want the value of "batchjobspec.exec" to be interpolated when I try to access it
Change your class BatchJobSpec
to:
class BatchJobSpec:
def __init__(self, pythoncode , batch):
self.pythoncode = pythoncode
self.name = batch.get("name")
self._exec = batch.get("exec")
@property
def exec(self):
to_exec = 'foo'
self.__setattr__(to_exec, lambda : exec('print("Hello world")'))
self.foo()
return None