Commit 5eb44e82 authored by Douglas's avatar Douglas

erp5_data_notebook: environment object implementation and refactoring to ERP5 kernel

- An environment object was implemented to help us deal with the multiprocess
architecture of ERP5 and objects that cannot be easily stored in the ZODB.
It stores definition of functions, classes and variables as string. The implementation
uses a dumb Environment class to allow users to make `define` and `undefine` calls,
which are captured and processed by an AST transformer before code execution.

- Along with the environment object, an automatic "import fixer" was created. It does
not allow users to import modules as they normally would, because this may cause
collateral effects on other users' code. A good example is the plot settings in the
matplotlib module. It will fix normal imports, make them use the environment object
mentione earlier automatically and warn the user about it.

A few bugs were fixed with this implementation, for example:

- https://nexedi.erp5.net/bug_module/20160318-7098DD, which reports an inconsistency
on portal catalog queries between Jupyter and Python (Script) objects. Probably an
issue with user context storage in ActiveProcess

- https://nexedi.erp5.net/bug_module/20160330-13AA193, which reports an error related
to acquisition when trying to plot images, which happened in other situations, although
this is not officially reported in Nexedi's ERP5. This probably also was happening because
of old user context storage.
parent 1aa8b2a6
...@@ -126,6 +126,23 @@ portal.%s() ...@@ -126,6 +126,23 @@ portal.%s()
# Test that calling Base_runJupyter shouldn't change the context Title # Test that calling Base_runJupyter shouldn't change the context Title
self.assertNotEqual(portal.getTitle(), new_test_title) self.assertNotEqual(portal.getTitle(), new_test_title)
def testJupyterCompileInvalidPythonSyntax(self):
"""
Test how the JupyterCompile extension behaves when it receives Python
code to be executed that has invalid syntax.
"""
self.login('dev_user')
jupyter_code = "a = 1\na++"
reference = 'Test.Notebook.ErrorHandling.SyntaxError'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
result_json = json.loads(result)
self.assertEquals(result_json['ename'], 'SyntaxError')
def testUserCannotAccessBaseExecuteJupyter(self): def testUserCannotAccessBaseExecuteJupyter(self):
""" """
Test if non developer user can't access Base_executeJupyter Test if non developer user can't access Base_executeJupyter
...@@ -321,26 +338,6 @@ portal.%s() ...@@ -321,26 +338,6 @@ portal.%s()
expected_result = '11' expected_result = '11'
self.assertEquals(json.loads(result)['code_result'].rstrip(), expected_result) self.assertEquals(json.loads(result)['code_result'].rstrip(), expected_result)
def testBaseExecuteJupyterWithContextObjectsAsLocalVariables(self):
"""
Test Base_executeJupyter with context objects as local variables
"""
portal = self.portal
self.login('dev_user')
python_expression = 'a=context.getPortalObject(); print a.getTitle()'
reference = 'Test.Notebook.ExecutePythonExpressionWithVariables %s' % time.time()
title = 'Test NB Title %s' % time.time()
result = portal.Base_executeJupyter(
title=title,
reference=reference,
python_expression=python_expression
)
self.tic()
expected_result = portal.getTitle()
self.assertEquals(json.loads(result)['code_result'].rstrip(), expected_result)
def testSavingModuleObjectLocalVariables(self): def testSavingModuleObjectLocalVariables(self):
""" """
Test to check the saving of module objects in local_variable_dict Test to check the saving of module objects in local_variable_dict
...@@ -393,7 +390,7 @@ image = context.portal_catalog.getResultValue(portal_type='Image',reference='%s' ...@@ -393,7 +390,7 @@ image = context.portal_catalog.getResultValue(portal_type='Image',reference='%s'
context.Base_renderAsHtml(image) context.Base_renderAsHtml(image)
"""%reference """%reference
local_variable_dict = {'imports' : {}, 'variables' : {}} local_variable_dict = {'setup' : {}, 'variables' : {}}
result = self.portal.Base_runJupyter( result = self.portal.Base_runJupyter(
jupyter_code=jupyter_code, jupyter_code=jupyter_code,
old_local_variable_dict=local_variable_dict old_local_variable_dict=local_variable_dict
...@@ -439,6 +436,187 @@ context.Base_renderAsHtml(image) ...@@ -439,6 +436,187 @@ context.Base_renderAsHtml(image)
) )
self.assertEquals(json.loads(result)['code_result'].rstrip(), 'sys') self.assertEquals(json.loads(result)['code_result'].rstrip(), 'sys')
def testEnvironmentObjectWithFunctionAndClass(self):
self.login('dev_user')
environment_define_code = '''
def create_sum_machines():
def sum_function(x, y):
return x + y
class Calculator(object):
def sum(self, x, y):
return x + y
return {'sum_function': sum_function, 'Calculator': Calculator}
environment.clearAll()
environment.define(create_sum_machines, 'creates sum function and class')
'''
reference = 'Test.Notebook.EnvironmentObject.Function'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=environment_define_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
jupyter_code = '''
print sum_function(1, 1)
print Calculator().sum(2, 2)
'''
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
self.tic()
result = json.loads(result)
output = result['code_result']
self.assertEquals(result['status'], 'ok')
self.assertEquals(output.strip(), '2\n4')
def testEnvironmentObjectSimpleVariable(self):
self.login('dev_user')
environment_define_code = '''
environment.clearAll()
environment.define(x='couscous')
'''
reference = 'Test.Notebook.EnvironmentObject.Variable'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=environment_define_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
jupyter_code = 'print x'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
self.tic()
result = json.loads(result)
self.assertEquals(result['status'], 'ok')
self.assertEquals(result['code_result'].strip(), 'couscous')
def testEnvironmentUndefineFunctionClass(self):
self.login('dev_user')
environment_define_code = '''
def create_sum_machines():
def sum_function(x, y):
return x + y
class Calculator(object):
def sum(self, x, y):
return x + y
return {'sum_function': sum_function, 'Calculator': Calculator}
environment.clearAll()
environment.define(create_sum_machines, 'creates sum function and class')
'''
reference = 'Test.Notebook.EnvironmentObject.Function.Undefine'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=environment_define_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
undefine_code = '''
environment.undefine('creates sum function and class')
'''
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=undefine_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
jupyter_code = '''
print 'sum_function' in locals()
print 'Calculator' in locals()
'''
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
result = json.loads(result)
output = result['code_result']
self.assertEquals(result['status'], 'ok')
self.assertEquals(output.strip(), 'False\nFalse')
def testEnvironmentUndefineVariable(self):
self.login('dev_user')
environment_define_code = '''
environment.clearAll()
environment.define(x='couscous')
'''
reference = 'Test.Notebook.EnvironmentObject.Variable.Undefine'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=environment_define_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
undefine_code = 'environment.undefine("x")'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=undefine_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
jupyter_code = "'x' in locals()"
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
self.tic()
result = json.loads(result)
self.assertEquals(result['status'], 'ok')
self.assertEquals(result['code_result'].strip(), 'False')
def testImportFixer(self):
self.login('dev_user')
import_code = '''
import random
'''
reference = 'Test.Notebook.EnvironmentObject.ImportFixer'
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=import_code
)
self.tic()
self.assertEquals(json.loads(result)['status'], 'ok')
jupyter_code = '''
print random.randint(1,1)
'''
result = self.portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
)
self.tic()
result = json.loads(result)
self.assertEquals(result['status'], 'ok')
self.assertEquals(result['code_result'].strip(), '1')
def testPivotTableJsIntegration(self): def testPivotTableJsIntegration(self):
''' '''
This test ensures the PivotTableJs user interface is correctly integrated This test ensures the PivotTableJs user interface is correctly integrated
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment