Skip to content

Commit

Permalink
Added teh ability for BlockCanvas to take custom UIs for each block a…
Browse files Browse the repository at this point in the history
…nd implicitly read inputs from this UI.
  • Loading branch information
ascopatz committed Oct 10, 2010
1 parent bcfc2e1 commit ae1d9e6
Show file tree
Hide file tree
Showing 21 changed files with 1,791 additions and 16 deletions.
711 changes: 711 additions & 0 deletions dummy.txt

Large diffs are not rendered by default.

56 changes: 50 additions & 6 deletions enthought/block_canvas/app/app.py
Expand Up @@ -30,6 +30,8 @@
from enthought.block_canvas.function_tools.function_call import FunctionCall
from enthought.block_canvas.function_tools.general_expression import GeneralExpression

from enthought.block_canvas.class_tools.class_library import ClassLibrary

# Local, relative imports
from experiment import Experiment
from project import Project
Expand Down Expand Up @@ -331,12 +333,14 @@ def add_function_object_to_model(self, item, x=None, y=None):
# fixme: Short term for testing. Remove imports in future and
# replace with FunctionCall UI.
function = LocalFunctionInfo(code=code)
node = FunctionCall.from_callable_object(function)
traits_class = self.match_function_to_traits_class(item.name)
node = FunctionCall.from_callable_object(function, traits_class)

else:
function = PythonFunctionInfo(name=item.name,
module=item.module)
node = FunctionCall.from_callable_object(function)
traits_class = self.match_function_to_traits_class(item.name)
node = FunctionCall.from_callable_object(function, traits_class)

# Bring up the dialog box to edit it
node.edit_traits(kind="modal")
Expand All @@ -359,6 +363,36 @@ def add_function_to_execution_model(self, function_call,
self.project.active_experiment.exec_model.add_function(function_call)
return

def match_function_to_traits_class(self, function_name):
"""Finds a class whose name matches '_{function_name}_view'.
Returns this class's traits_view attribute, if present.
Returns None otherwise."""

# Search the class library for a matching name
for a_class in self.class_library.classes:
if a_class.name == '_{0}_view'.format(function_name):
traits_class = self.get_traits_class(a_class)
return traits_class

# Couldn't find a class to match the function.
return None

def get_traits_class(self, a_class):
"""Imports a class from its module and name and returns it, as long as it is a subclass of HasTraits.
"""
# Import the class from the module
_module = __import__(a_class.module, globals(), locals(), [a_class.name], -1)
traits_class = getattr(_module, a_class.name)

# Make sure the class has traits at all
if issubclass(traits_class, HasTraits):
# Get new instance of this class
tc = traits_class()
return tc

# Class does not have traits!
return None

def remove_function_from_execution_model(self, function_call):
"""Remove a function from the execution model"""
#FIXME: Should this take a UUID instead of a reference?
Expand Down Expand Up @@ -598,8 +632,18 @@ def _file_directory_default(self):
logging.getLogger().addHandler(logging.StreamHandler())
logging.getLogger().setLevel(logging.DEBUG)

library = FunctionLibrary(modules=['os'])
search = FunctionSearch(all_functions=library.functions)
app = Application(code=code, data_context=DataContext(name='data'),
function_library=library, function_search=search)
modules = ['os']

class_library = ClassLibrary(modules=modules)
func_library = FunctionLibrary(modules=modules)
func_search = FunctionSearch(all_functions=func_library.functions)

app = Application(
code=code,
data_context=DataContext(name='data'),
class_library=class_library,
function_library=func_library,
function_search=func_search,
)

app.configure_traits()
49 changes: 48 additions & 1 deletion enthought/block_canvas/block_display/execution_model.py
Expand Up @@ -227,7 +227,47 @@ def execute(self, context, globals=None, inputs=None, outputs=None):
restricted = self.__class__(statements=good_statements)

try:
exec restricted.code in globals, context
# Uncomment to execute the whole block in one go.
# Does not allow for traits-based definition
### exec restricted.code in globals, context

# Generate a running context for code as a whole.
# This is based off of the given context.
running_context = {}
running_context.update(context)

# Grab the required imports and definitions
exec restricted.imports_and_locals in globals, running_context
running_context.update(running_context)

# Run each statement individually, adding it to the
# running context.
# This allows each statement to to be executed in it own
# context! If default values are specified by the user
# via and associated HasTraits class, we add this class's
# context to just that statement
for stmt in restricted.sorted_statements:
# Make sure the statment is a function_call
if hasattr(stmt, 'inputs_view_class'):
# Check if there are UI valuese to apply.
if stmt.inputs_view_class == None:
exec stmt.call_signature in globals, running_context
running_context.update(running_context)
else:
# Create a context for just this statement based on the
# User interface.
stmt_context = {}
stmt_context.update(running_context)
stmt_context.update(stmt.inputs_view_class.__dict__)
exec stmt.call_signature in globals, stmt_context
running_context.update(stmt_context)
else:
exec stmt.call_signature in globals, running_context
running_context.update(running_context)

# Add the running context back to the original context
context.update(running_context)

except Exception, e:
print 'Got exception from code:'
print restricted.code
Expand Down Expand Up @@ -359,6 +399,13 @@ def mark_unsatisfied_inputs(self, available_names):
for stmt in self.statements:
for ov in stmt.outputs:
available_names.add(ov.binding)

if hasattr(stmt, 'inputs_view_class'):
# Might want to do some more checking here
ivc = getattr(stmt, 'inputs_view_class')
if ivc != None:
for key in ivc.__dict__:
available_names.add(key)
# Add the builtins, too.
available_names.update(builtin_names)

Expand Down
Empty file.
181 changes: 181 additions & 0 deletions enthought/block_canvas/class_tools/class_library.py
@@ -0,0 +1,181 @@
# System library imports
import os

# Enthought library imports
from enthought.traits.api import (HasTraits, Str, List, Dict,
Callable, implements)

# Local imports
from search_package import find_classes
from enthought.block_canvas.function_tools.search_package import get_module_path
from i_minimal_class_info import MinimalClassInfo

class ClassLibrary(HasTraits):
""" Keep track of a list of modules/packages to search for classes.
The typical tree cases for a module are:
1. The module is a pure python module, and we can search its code.
2. The module is an extension module, so it has a file, but it is
not python code we can parse. We have to import this
type of module to find its functions.
3. The module is a builtin module (like sys), and it doesn't have
a file. Like an extension module, this must be imported
and searched.
4. The module is a package name. In this case, we search all the
modules the package contains.
5. [don't handle currently] The specified value is a file or a
directory name.
"""

#fixme: I'm not sure this will handle zip files imports correctly.
#fixme: We do not do anything to try and update a cache if a module
# on disk changes.
#fixme: If we cached module file names, their time stamp, and
# the functions included in them, startup would be much faster.

##########################################################################
# FunctionLibrary traits
##########################################################################

# List of strings such as foo.bar that specifies which modules to search.
modules = List(Str)

# List of the classes found in the specified modules/pacakges.
# Each class is represented by an object with 'name' and 'module'
# attributes.
classes = List

# A factory function for generating items in the function lists. The
# only requirements is that it take 'module' and 'name' as keyword args.
# fixme: If someone assigns a function into this, will pickling break?
class_factory = Callable(MinimalClassInfo)

# Keep class list for modules cached so that we don't have search
# for them each time there is an update.
# fixme: Be careful about this so that we don't end up caching things
# that the user may be editing.
_module_cache = Dict


##########################################################################
# FunctionLibrary interface
##########################################################################

def refresh_module(self, module=None):
""" Reparse the module,specified as a string , looking for classes.
If module=None, then all strings in the modules string are
re-searched. If module was not previously in the list of
modules, searched, it is added.
ClassLibrary maintains a cache so that it doesn't have to
search modules that it has recently parsed. This method
forces a refresh of this cache.
"""
# fixme: handle case when module name is bad.

if module is None:
# Update all modules. Clear cache, and update the class list.
self._module_cache[module] = {}
self._update_class_list()
else:
# Find the classes in the module and put them in the cache.
clss = self._classes_from_module(self, module)
self._module_cache[module] = clss

if module not in self.modules:
# This forces an update of the class list, so we don't
# do it explicitly in this code path.
self.modules.append(module)
else:
# We'll rebuild the class list from scratch, but it will
# be fast because everything is cached.
self._update_function_list()


### private interface ####################################################

def _classes_from_module(self, module):

# Test whether this module imports from a Pure python file.
# If it isn't represented as a file, we'll need to
# use the import method of searching for classes.
path = get_module_path(module)

# fixme: What about zip files.

if (os.path.exists(path) and
os.path.splitext(path)[1] == '.py'):
# If it is a file, then parse the ast.
import_method = False
elif (os.path.exists(path) and
os.path.isdir(path)):
# If it is a package directory, parse the ast.
import_method = False
else:
# Must be extension module or built-in, or something else...
# Use import to handle it.
import_method = True

mod_and_name = find_classes(module, import_method)
clss = [self.class_factory(module=module,name=name)
for module, name in mod_and_name]
return clss


def _update_class_list(self):
classes = []
for module in self.modules:
# First, try the cache.
if self._module_cache.has_key(module):
clss = self._module_cache[module]
else:
# Otherwise, we need to search the module.
clss = self._classes_from_module(module)
# fixme: We need to filter the cache based on some
# items we never want to cache.
self._module_cache[module] = clss
classes.extend(clss)

self.classes = classes

### trait listeners ######################################################

def _modules_changed(self):
self._update_class_list()

def _modules_items_changed(self):
self._update_class_list()



if __name__ == "__main__":
library = ClassLibrary(modules=['os'])
print library.modules
print library.classes

library.modules.append('sys')
print library.modules
print library.classes

library.modules.append('_socket')
print library.modules
print library.classes

print 'including cp:'
import time
t1 = time.clock()
library.modules.append('xml')
library.modules.append('cp')
print library.modules
print library.classes
t2 = time.clock()
print t2-t1

old_classes = library.classes
t1 = time.clock()
library.modules = ['cp','xml','_socket','sys','os']
assert(len(old_classes) == len(library.classes))
t2 = time.clock()
print t2-t1

0 comments on commit ae1d9e6

Please sign in to comment.