我正在尝试动态执行一些用户提供的代码,该代码定义了两种方法。 on_data
和initialise
。 (该代码旨在由用户在自己的计算机上本地运行,因此安全性无关紧要。)
为此,我使用exec执行代码字符串,这按预期会导致用户代码中定义的方法位于本地范围内。使用dir()确认了这一点:
['db_session', 'initialise', 'on_data', 'req', 'strategy_id', 'strategy_rec']
您可以看到存在初始化和on_data。跳到下一行,我尝试动态创建任务并分配这两种方法,然后收到此错误:
exec(strategy_rec.execution_code)
print(dir())
UserStrat = type("UserStrat", (core.strategy.ABStrategy, ), {
> "initialise": initialise,
"on_data": on_data
})
E NameError: name 'initialise' is not defined
我对此有些茫然。我认为这可能是某处发生unicode变量名称的唯一原因,但我已经重写了几次标识符。
任何指针都会很好。这也适用于一个开源项目:https://github.com/octible/algobox/tree/master/service_backtest
这是发生错误的函数代码:
def strategy_execute(strategy_id):
db_session = give_session()
req = GetOrThrowDict(request.get_json(force=True))
strategy_rec = db_session.query(Strategy).filter_by(id=strategy_id).one()
# try:
exec(strategy_rec.execution_code)
print(dir())
# outs >>> ['db_session', 'initialise', 'on_data', 'req', 'strategy_id', 'strategy_rec']
UserStrat = type("UserStrat", (core.strategy.ABStrategy, ), {
"initialise": initialise,
"on_data": on_data
})
这是测试的代码:
def test_run_algorithm(client, psql):
strat_code = """
def initialise(self):
pass
def on_data(self, context, update):
return {"signal": core.signal.random()}
"""
create_response = client.post("/strategy/", json={
"name": "Lambo by Now",
"execution_code": strat_code,
"data_format": "CANDLE",
"subscribes_to": ["GDAX:BTC-USD:5M"],
"lookback_period": 0
})
created_dict = create_response.json
print(created_dict)
assert create_response.status_code == 201
data = {"context": [], "update": {
"datetime": 1551398400,
"open": 100,
"close": 101,
"high": 102,
"low": 98.20,
"topic": "GDAX:BTC-USD:5M"
}
}
call_response = client.post("/strategy/execute/{}".format(created_dict["id"]),
json=data)
import pdb; pdb.set_trace()
assert "signal" in call_response.json
这是完整的pytest输出:
========================================== test session starts ==========================================
platform linux -- Python 3.6.8, pytest-4.3.0, py-1.7.0, pluggy-0.8.1
rootdir: /home/callam/Documents/projects/algobox, inifile:
plugins: cov-2.6.1
collected 3 items
service_strategy/test/integration/test_algorithm.py ..F [100%]
=============================================== FAILURES ================================================
__________________________________________ test_run_algorithm ___________________________________________
client = <FlaskClient <Flask 'service'>>
psql = {'container': <testcontainers.postgres.PostgresContainer object at 0x7f58f5b6de80>, 'session': <sqlalchemy.orm.session.Session object at 0x7f58f5ad0320>}
def test_run_algorithm(client, psql):
strat_code = """
def initialise(self):
pass
def on_data(self, context, update):
return {"signal": core.signal.random()}
"""
create_response = client.post("/strategy/", json={
"name": "Lambo by Now",
"execution_code": strat_code,
"data_format": "CANDLE",
"subscribes_to": ["GDAX:BTC-USD:5M"],
"lookback_period": 0
})
created_dict = create_response.json
print(created_dict)
assert create_response.status_code == 201
data = {"context": [], "update": {
"datetime": 1551398400,
"open": 100,
"close": 101,
"high": 102,
"low": 98.20,
"topic": "GDAX:BTC-USD:5M"
}
}
call_response = client.post("/strategy/execute/{}".format(created_dict["id"]),
> json=data)
service_strategy/test/integration/test_algorithm.py:112:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../venvs/algobox/lib/python3.6/site-packages/werkzeug/test.py:840: in post
return self.open(*args, **kw)
../../venvs/algobox/lib/python3.6/site-packages/flask/testing.py:200: in open
follow_redirects=follow_redirects
../../venvs/algobox/lib/python3.6/site-packages/werkzeug/test.py:803: in open
response = self.run_wsgi_app(environ, buffered=buffered)
../../venvs/algobox/lib/python3.6/site-packages/werkzeug/test.py:716: in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
../../venvs/algobox/lib/python3.6/site-packages/werkzeug/test.py:923: in run_wsgi_app
app_rv = app(environ, start_response)
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:2309: in __call__
return self.wsgi_app(environ, start_response)
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:2295: in wsgi_app
response = self.handle_exception(e)
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:1741: in handle_exception
reraise(exc_type, exc_value, tb)
../../venvs/algobox/lib/python3.6/site-packages/flask/_compat.py:35: in reraise
raise value
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:2292: in wsgi_app
response = self.full_dispatch_request()
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:1815: in full_dispatch_request
rv = self.handle_user_exception(e)
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:1718: in handle_user_exception
reraise(exc_type, exc_value, tb)
../../venvs/algobox/lib/python3.6/site-packages/flask/_compat.py:35: in reraise
raise value
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:1813: in full_dispatch_request
rv = self.dispatch_request()
../../venvs/algobox/lib/python3.6/site-packages/flask/app.py:1799: in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
strategy_id = 'e542039f-b1f1-4387-af83-2fb763c79717'
@strategy_bp.route("/execute/<strategy_id>", methods=["POST"])
def strategy_execute(strategy_id):
"""
This is a bit like the observer pattern but with multiple services involved.
Expects to receive the following from the data or backtesting service.
{
"update": {},
"context": [],
"topic": "GDAX:BTC-USD:5M",
}
"""
db_session = give_session()
req = GetOrThrowDict(request.get_json(force=True))
strategy_rec = db_session.query(Strategy).filter_by(id=strategy_id).one()
# try:
exec(strategy_rec.execution_code)
print(dir())
UserStrat = type("UserStrat", (core.strategy.ABStrategy, ), {
> "initialise": initialise,
"on_data": on_data
})
E NameError: name 'initialise' is not defined
service_strategy/service/strategy.py:123: NameError
----------------------------------------- Captured stdout setup -----------------------------------------
Pulling image postgres:9.6
Container started: bcd9f1e79f
Waiting to be ready...
----------------------------------------- Captured stdout call ------------------------------------------
{'active': True, 'data_format': 'CANDLE', 'execution_code': '\ndef initialise(self):\n pass\n\ndef on_data(self, context, update):\n return {"signal": core.signal.random()}\n', 'id': 'e542039f-b1f1-4387-af83-2fb763c79717', 'lookback_period': 30, 'name': 'Lambo by Now', 'subscribes_to': '{GDAX:BTC-USD:5M}'}
['db_session', 'initialise', 'on_data', 'req', 'strategy_id', 'strategy_rec']
=========================================== warnings summary ============================================
/home/callam/Documents/venvs/algobox/lib/python3.6/site-packages/psycopg2/__init__.py:144
/home/callam/Documents/venvs/algobox/lib/python3.6/site-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
""")
-- Docs: https://docs.pytest.org/en/latest/warnings.html
============================ 1 failed, 2 passed, 1 warnings in 15.04 seconds ============================