Commit 9b156fc2 authored by Lucas Bourneuf's avatar Lucas Bourneuf

module is much more self-aware, now containing and accessing its own code

this opens the ability to change module.source_code after its creation
and get the obvious effect of having the new code used instead of the
initial one.
parent 68426203
from .module_loader import Script from .module_loader import Script
from .core import run, single_image_from_filenames, multiple_images_from_filenames, gif_from_filenames, compile_to_single_image, compile_to_images from .core import run, single_image_from_filenames, multiple_images_from_filenames, gif_from_filenames, compile_to_single_image, compile_to_images, compile_context_to_dots
from .module_loader import build_scripts_from_file, build_scripts_from_dir from .module_loader import build_scripts_from_file, build_scripts_from_dir
......
...@@ -153,14 +153,11 @@ def compile_to_images(context:str, outfile_template:str=None, dotfile_template:s ...@@ -153,14 +153,11 @@ def compile_to_images(context:str, outfile_template:str=None, dotfile_template:s
dotfile_template -- template name for dot files, or None dotfile_template -- template name for dot files, or None
""" """
configs = asp_to_dot.visual_config_from_asp(
solve_context(context, nb_model=nb_model)
)
if outfile_template and '{}' not in outfile_template: if outfile_template and '{}' not in outfile_template:
raise ValueError("Outfile argument is not a valid template") raise ValueError("Outfile argument is not a valid template")
if dotfile_template and '{}' not in dotfile_template: if dotfile_template and '{}' not in dotfile_template:
raise ValueError("Dotfile argument is not a valid template") raise ValueError("Dotfile argument is not a valid template")
dots = dot_writer.multiple_graphs_from_configs(configs) dots = compile_context_to_dots(context, nb_model)
for idx, dot in enumerate(dots, start=1): for idx, dot in enumerate(dots, start=1):
del_outfile = False del_outfile = False
if outfile_template is None: if outfile_template is None:
...@@ -176,3 +173,11 @@ def compile_to_images(context:str, outfile_template:str=None, dotfile_template:s ...@@ -176,3 +173,11 @@ def compile_to_images(context:str, outfile_template:str=None, dotfile_template:s
if del_outfile: if del_outfile:
os.unlink(outfile) os.unlink(outfile)
yield img yield img
def compile_context_to_dots(context:str, nb_model:int=0) -> [str]:
"Yield a dot string for each found model"
configs = asp_to_dot.visual_config_from_asp(
solve_context(context, nb_model=nb_model)
)
yield from dot_writer.multiple_graphs_from_configs(configs)
...@@ -28,7 +28,7 @@ class ScriptError(ValueError): ...@@ -28,7 +28,7 @@ class ScriptError(ValueError):
pass pass
Script = namedtuple('Script', 'name, tags, description, module, run_on, options, options_values, input_mode, incompatible, active_by_default, spec_inputs, spec_outputs, inputs, outputs, source_view, disabled, erase_context') Script = namedtuple('Script', 'name, tags, description, module, run_on, options, options_values, input_mode, incompatible, active_by_default, spec_inputs, spec_outputs, inputs, outputs, source_view, source_code, language, disabled, erase_context')
# name -- human readable name # name -- human readable name
# tags -- set of tags identifying the script # tags -- set of tags identifying the script
# description -- human readable and high level description of the script # description -- human readable and high level description of the script
...@@ -44,6 +44,8 @@ Script = namedtuple('Script', 'name, tags, description, module, run_on, options, ...@@ -44,6 +44,8 @@ Script = namedtuple('Script', 'name, tags, description, module, run_on, options,
# inputs -- function in module to call to get all possible inputs # inputs -- function in module to call to get all possible inputs
# outputs -- function in module to call to get all possible outputs # outputs -- function in module to call to get all possible outputs
# source_view -- None or human readable representation of module's source code # source_view -- None or human readable representation of module's source code
# source_code -- string containing the source code
# language -- string indicating the language implementing the source code
# disabled -- true if the script must be ignored # disabled -- true if the script must be ignored
# erase_context -- true if the script erase the context (default: false, context is kept) # erase_context -- true if the script erase the context (default: false, context is kept)
...@@ -197,6 +199,7 @@ def build_scripts_from_asp_code(data:str or list) -> [Script]: ...@@ -197,6 +199,7 @@ def build_scripts_from_asp_code(data:str or list) -> [Script]:
yield build_script_from_json({ yield build_script_from_json({
'name': 'inline ASP code', 'name': 'inline ASP code',
'ASP': data, 'ASP': data,
'language': 'asp',
'description': 'inline ASP code', 'description': 'inline ASP code',
'inputs': [], 'inputs': [],
'outputs': [], # TODO: search for #show's in the file 'outputs': [], # TODO: search for #show's in the file
...@@ -227,7 +230,7 @@ def build_script_from_json(module_def:dict) -> Script: ...@@ -227,7 +230,7 @@ def build_script_from_json(module_def:dict) -> Script:
module.__doc__ = module_def.get('description', DEFAULT_DOC) module.__doc__ = module_def.get('description', DEFAULT_DOC)
# run_on # run_on
def build_run_on(module_def): def build_run_on(module, module_def):
if 'ASP file' in module_def: if 'ASP file' in module_def:
fname = module_def['ASP file'] fname = module_def['ASP file']
if not os.path.exists(fname): if not os.path.exists(fname):
...@@ -236,24 +239,27 @@ def build_script_from_json(module_def:dict) -> Script: ...@@ -236,24 +239,27 @@ def build_script_from_json(module_def:dict) -> Script:
def run_on(context:str): def run_on(context:str):
assert isinstance(context, str), (type(context), context) assert isinstance(context, str), (type(context), context)
with open(fname) as fd: with open(fname) as fd:
return fd.read() module.source_code = fd.read()
return module.source_code
module.language = 'asp'
module_def['erase_context'] = False module_def['erase_context'] = False
elif 'ASP' in module_def: elif 'ASP' in module_def:
asp_code = module_def['ASP'] module.source_code = module_def['ASP']
if os.path.exists(asp_code): if os.path.exists(module.source_code):
raise ScriptError("JSON script {} put an ASP file ({}) as raw " raise ScriptError("JSON script {} put an ASP file ({}) as raw "
"ASP code.".format(module.NAME, asp_code)) "ASP code.".format(module.NAME, module.source_code))
def run_on(context:str): def run_on(context:str):
return asp_code return module.source_code
module_def['erase_context'] = False module_def['erase_context'] = False
module.source_view = asp_code module.language = 'asp'
module.source_view = module.source_code # TODO: deletable ?
elif 'python' in module_def or 'python file' in module_def: elif 'python' in module_def or 'python file' in module_def:
if 'python' in module_def: if 'python' in module_def:
pycode = module_def['python'] pycode = module_def['python']
if os.path.exists(pycode): # that's irregular, but we can stand if os.path.exists(pycode): # that's irregular, but we can stand
module_def['python file'] = module_def['python'] module_def['python file'] = module_def['python']
del module_def['python'] del module_def['python']
return build_run_on(module_def) return build_run_on(module, module_def)
else: # it's a file, not raw code else: # it's a file, not raw code
fname = module_def['python file'] fname = module_def['python file']
if not os.path.exists(fname): if not os.path.exists(fname):
...@@ -261,17 +267,19 @@ def build_script_from_json(module_def:dict) -> Script: ...@@ -261,17 +267,19 @@ def build_script_from_json(module_def:dict) -> Script:
"doesn't exists.".format(module.NAME, fname)) "doesn't exists.".format(module.NAME, fname))
with open(fname) as fd: with open(fname) as fd:
pycode = fd.read() pycode = fd.read()
code = 'def func(models=models):\n\t' + '\t'.join(pycode.splitlines(True)) pycode = 'def func(models=models):\n\t' + '\t'.join(pycode.splitlines(True))
code = utils.compile_python_code(code) module.source_code = pycode
def run_on(context:str, pycode:'code'=code): pycode = utils.compile_python_code(pycode)
def run_on(context:str):
assert isinstance(context, str), (type(context), context) assert isinstance(context, str), (type(context), context)
namespace = {'models': clyngor.solve((), inline=context).by_predicate} namespace = {'models': clyngor.solve(inline=context).by_predicate}
try: try:
utils.run_compiled_python_code(pycode, namespace) utils.run_compiled_python_code(module.source_code, namespace)
return utils.join_on_genstr(namespace['func'])() return utils.join_on_genstr(namespace['func'])()
except: except:
print('Imported Python error:', traceback.format_exc()) print(f'Imported Python error in module {module.name}:\n', traceback.format_exc())
module.source_view = pycode # just the user written part, not the function encapsulation module.language = 'python'
module.source_view = pycode # TODO: deletable ?
else: else:
raise ValueError("JSON script {} do not have any code field ('ASP' " raise ValueError("JSON script {} do not have any code field ('ASP' "
"or 'ASP file' for instance). If this script was " "or 'ASP file' for instance). If this script was "
...@@ -279,7 +287,7 @@ def build_script_from_json(module_def:dict) -> Script: ...@@ -279,7 +287,7 @@ def build_script_from_json(module_def:dict) -> Script:
"using an older version than the script creator." "using an older version than the script creator."
"".format(module.NAME)) "".format(module.NAME))
return run_on return run_on
module.run_on = build_run_on(module_def) module.run_on = build_run_on(module, module_def)
return build_script_from_module(module) return build_script_from_module(module)
...@@ -347,7 +355,9 @@ def build_script_from_module(module) -> Script or ScriptError: ...@@ -347,7 +355,9 @@ def build_script_from_module(module) -> Script or ScriptError:
for arg, type, default in options) for arg, type, default in options)
# source view # source view
source_view = getattr(module, 'source_view', None) source_view = getattr(module, 'source_view', None) # TODO: deletable ?
source_code = getattr(module, 'source_code', None)
language = getattr(module, 'language', 'python')
# TODO: detect non keyword only parameters, and check their validity. # TODO: detect non keyword only parameters, and check their validity.
...@@ -371,6 +381,8 @@ def build_script_from_module(module) -> Script or ScriptError: ...@@ -371,6 +381,8 @@ def build_script_from_module(module) -> Script or ScriptError:
active_by_default=active_by_default, active_by_default=active_by_default,
**_build_and_validate_io(module, default_options), **_build_and_validate_io(module, default_options),
source_view=source_view, source_view=source_view,
source_code=source_code,
language=language,
disabled=disabled, disabled=disabled,
erase_context=bool(getattr(module, 'ERASE_CONTEXT', False)), erase_context=bool(getattr(module, 'ERASE_CONTEXT', False)),
) )
......
import itertools
from collections import defaultdict
from pydot import graph_from_dot_data
from biseau import run, compile_context_to_dots
from biseau.module_loader import build_scripts_from_asp_code from biseau.module_loader import build_scripts_from_asp_code
ASP_LINK = 'link(a,b).'
def test_basic(): def test_basic():
scripts = tuple(build_scripts_from_asp_code('link(a,b).')) scripts = tuple(build_scripts_from_asp_code(ASP_LINK))
assert len(scripts) == 1 assert len(scripts) == 1
script = next(iter(scripts)) script = next(iter(scripts))
assert script.description == 'inline ASP code' assert script.description == 'inline ASP code'
assert script.name == 'inline ASP code' assert script.name == 'inline ASP code'
assert script.input_mode is str assert script.input_mode is str
assert script.source_code == ASP_LINK
assert script.language == 'asp'
assert not script.options assert not script.options
assert not script.options_values assert not script.options_values
assert not script.erase_context assert not script.erase_context
def test_pipeline():
scripts = tuple((
*build_scripts_from_asp_code(ASP_LINK),
*build_scripts_from_asp_code('link(X,1..3) :- link(X,_).'),
))
assert len(scripts) == 2
final_dots = tuple(compile_context_to_dots(run(scripts)))
assert len(final_dots) == 1
final_dot = ''.join(final_dots[0])
graphs = tuple(_graphdict_from_dot(final_dot))
assert len(graphs) == 1
graph = graphs[0]
assert graph == {'a': {'b', '1', '2', '3'}}
def _graphdict_from_dot(dot:str) -> dict:
"Convert dot data into a dict describing the graph ; yield graph"
graphs = graph_from_dot_data(dot)
for graph in graphs:
dictgraph = defaultdict(set) # source: target
for edge in graph.get_edges():
dictgraph[edge.get_source()].add(edge.get_destination())
yield dict(dictgraph)
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