3 new revisions:

Revision: 3792387327c2
Author:   Pekka Klärck
Date:     Tue Feb 21 07:11:23 2012
Log: utils.importer: Allow instantiating imported classes automatically....
http://code.google.com/p/robotframework/source/detail?r=3792387327c2

Revision: 022da1a2fe47
Author:   Pekka Klärck
Date:     Tue Feb 21 07:12:59 2012
Log: Listeners: let importer to instantiate listener classes now that it ha...
http://code.google.com/p/robotframework/source/detail?r=022da1a2fe47

Revision: b2250eb66ea9
Author:   Pekka Klärck
Date:     Tue Feb 21 08:17:27 2012
Log: Implementation and tests for creating variable files as Python or Java...
http://code.google.com/p/robotframework/source/detail?r=b2250eb66ea9

==============================================================================
Revision: 3792387327c2
Author:   Pekka Klärck
Date:     Tue Feb 21 07:11:23 2012
Log: utils.importer: Allow instantiating imported classes automatically.

Update issue 1021
Status: Started
Now importer that also variable imports use can instantiate classes automatically. Next up is changing variable imports to use this functionality.
http://code.google.com/p/robotframework/source/detail?r=3792387327c2

Modified:
 /src/robot/utils/importer.py
 /utest/utils/test_importer_util.py

=======================================
--- /src/robot/utils/importer.py        Tue Feb 21 04:48:15 2012
+++ /src/robot/utils/importer.py        Tue Feb 21 07:11:23 2012
@@ -37,7 +37,7 @@
                            DottedImporter(logger))
         self._by_path_importer = self._importers[0]

-    def import_class_or_module(self, name):
+    def import_class_or_module(self, name, instantiate_with_args=None):
         """Imports Python class/module or Java class with given name.

Class can either live in a module/package or be standalone Java class.
@@ -51,21 +51,23 @@

`name` can also be a path to the imported file/directory. In that case
         importing is done using `import_class_or_module_by_path` method.
+
+        If `instantiate_with_args` is not None, imported classes are
+        instantiated with the specified arguments automatically.
         """
         try:
             imported, source = self._import_class_or_module(name)
+            self._log_import_succeeded(imported, name, source)
+ return self._instantiate_if_needed(imported, instantiate_with_args)
         except DataError, err:
             self._raise_import_failed(name, err)
-        else:
-            self._log_import_succeeded(imported, name, source)
-            return imported

     def _import_class_or_module(self, name):
         for importer in self._importers:
             if importer.handles(name):
                 return importer.import_(name)

-    def import_class_or_module_by_path(self, path):
+ def import_class_or_module_by_path(self, path, instantiate_with_args=None):
         """Import a Python module or Java class using a file system path.

         When importing a Python file, the path must end with '.py' and the
@@ -75,14 +77,16 @@
When importing Java classes, the path must end with '.java' or '.class'.
         The class file must exist in both cases and in the former case also
         the source file must exist.
+
+        If `instantiate_with_args` is not None, imported classes are
+        instantiated with the specified arguments automatically.
         """
         try:
             imported, source = self._by_path_importer.import_(path)
+            self._log_import_succeeded(imported, imported.__name__, source)
+ return self._instantiate_if_needed(imported, instantiate_with_args)
         except DataError, err:
             self._raise_import_failed(path, err)
-        else:
-            self._log_import_succeeded(imported, imported.__name__, source)
-            return imported

     def _raise_import_failed(self, name, error):
         import_type = '%s ' % self._type if self._type else ''
@@ -102,6 +106,21 @@
             if item:
                 yield '  %s' % item

+    def _instantiate_if_needed(self, imported, args):
+        if args is None:
+            return imported
+        if inspect.isclass(imported):
+            return self._instantiate_class(imported, args)
+        if args:
+            raise DataError("Modules do not take arguments.")
+        return imported
+
+    def _instantiate_class(self, imported, args):
+        try:
+            return imported(*args)
+        except:
+ raise DataError('Creating instance failed: %s\n%s' % get_error_details())
+
     def _log_import_succeeded(self, item, name, source):
         import_type = '%s ' % self._type if self._type else ''
         item_type = 'module' if inspect.ismodule(item) else 'class'
=======================================
--- /utest/utils/test_importer_util.py  Tue Jan  3 14:32:16 2012
+++ /utest/utils/test_importer_util.py  Tue Feb 21 07:11:23 2012
@@ -418,5 +418,46 @@
         self._verify('hello'+os.sep, 'hello')


+class TestInstantiation(unittest.TestCase):
+
+    def setUp(self):
+        self.tearDown()
+
+    def tearDown(self):
+        if exists(TESTDIR):
+            shutil.rmtree(TESTDIR)
+
+    def test_when_importing_by_name(self):
+        from ExampleLibrary import ExampleLibrary
+        lib = Importer().import_class_or_module('ExampleLibrary',
+                                                instantiate_with_args=())
+        assert_true(not inspect.isclass(lib))
+        assert_true(isinstance(lib, ExampleLibrary))
+
+    def test_with_arguments(self):
+ lib = Importer().import_class_or_module('libswithargs.Mixed', range(5))
+        assert_equals(lib.get_args(), (0, 1, '2 3 4'))
+
+    def test_when_importing_by_path(self):
+        path = create_temp_file('args.py', extra_content='class args: a=1')
+        lib = Importer().import_class_or_module_by_path(path, ())
+        assert_true(not inspect.isclass(lib))
+        assert_equals(lib.__class__.__name__, 'args')
+        assert_equals(lib.a, 1)
+
+    def test_instantiate_failure(self):
+        err = assert_raises(DataError, Importer().import_class_or_module,
+                            'ExampleLibrary', ['accepts', 'no', 'args'])
+ assert_true(unicode(err).startswith("Importing 'ExampleLibrary' failed: " + "Creating instance failed: TypeError:"))
+
+    def test_modules_do_not_take_arguments(self):
+        path = create_temp_file('no_args_allowed.py')
+        assert_raises_with_msg(DataError,
+ "Importing '%s' failed: Modules do not take arguments." % path,
+                               Importer().import_class_or_module_by_path,
+                               path, ['invalid'])
+
+
 if __name__ == '__main__':
     unittest.main()

==============================================================================
Revision: 022da1a2fe47
Author:   Pekka Klärck
Date:     Tue Feb 21 07:12:59 2012
Log: Listeners: let importer to instantiate listener classes now that it has that capability
http://code.google.com/p/robotframework/source/detail?r=022da1a2fe47

Modified:
 /atest/robot/output/listener_interface/importing_listeners.txt
 /atest/robot/output/listener_interface/old_importing_listeners.txt
 /src/robot/output/listeners.py

=======================================
--- /atest/robot/output/listener_interface/importing_listeners.txt Tue Jan 3 04:26:44 2012 +++ /atest/robot/output/listener_interface/importing_listeners.txt Tue Feb 21 07:12:59 2012
@@ -28,9 +28,11 @@
 Listener With Wrong Number Of Arguments
     [Template]    Check Syslog Contains
     Taking listener 'listeners.WithArgs' into use failed:
+    ...    Importing listener 'listeners.WithArgs' failed:
     ...    Creating instance failed:
... TypeError: __init__() takes at least 2 arguments (1 given)${EMPTY TB}
     Taking listener 'listeners.WithArgs:1:2:3' into use failed:
+    ...    Importing listener 'listeners.WithArgs' failed:
     ...    Creating instance failed:
... TypeError: __init__() takes at most 3 arguments (4 given)${EMPTY TB}

@@ -54,9 +56,11 @@
     [Tags]  jybot
     [Template]    Check Syslog Contains
     Taking listener 'JavaListenerWithArgs' into use failed:
+    ...    Importing listener 'JavaListenerWithArgs' failed:
     ...    Creating instance failed:
... TypeError: JavaListenerWithArgs(): expected 2 args; got 0${EMPTY TB}
     Taking listener 'JavaListenerWithArgs:b:a:r' into use failed:
+    ...    Importing listener 'JavaListenerWithArgs' failed:
     ...    Creating instance failed:
... TypeError: JavaListenerWithArgs(): expected 2 args; got 3${EMPTY TB}

=======================================
--- /atest/robot/output/listener_interface/old_importing_listeners.txt Tue Jan 3 04:26:44 2012 +++ /atest/robot/output/listener_interface/old_importing_listeners.txt Tue Feb 21 07:12:59 2012
@@ -28,9 +28,11 @@
 Listener With Wrong Number Of Arguments
     [Template]    Check Syslog contains
     Taking listener 'old_listeners.WithArgs' into use failed:
+    ...    Importing listener 'old_listeners.WithArgs' failed:
     ...    Creating instance failed:
... TypeError: __init__() takes at least 2 arguments (1 given)${EMPTY TB}
     Taking listener 'old_listeners.WithArgs:1:2:3' into use failed:
+    ...    Importing listener 'old_listeners.WithArgs' failed:
     ...    Creating instance failed:
... TypeError: __init__() takes at most 3 arguments (4 given)${EMPTY TB}

@@ -54,9 +56,11 @@
     [Tags]  jybot
     [Template]    Check Syslog contains
     Taking listener 'OldJavaListenerWithArgs' into use failed:
+    ...    Importing listener 'OldJavaListenerWithArgs' failed:
     ...    Creating instance failed:
... TypeError: OldJavaListenerWithArgs(): expected 2 args; got 0${EMPTY TB}
     Taking listener 'OldJavaListenerWithArgs:b:a:r' into use failed:
+    ...    Importing listener 'OldJavaListenerWithArgs' failed:
     ...    Creating instance failed:
... TypeError: OldJavaListenerWithArgs(): expected 2 args; got 3${EMPTY TB}

=======================================
--- /src/robot/output/listeners.py      Wed Jan  4 02:49:59 2012
+++ /src/robot/output/listeners.py      Tue Feb 21 07:12:59 2012
@@ -218,19 +218,8 @@

     def _import_listener(self, name, args):
         importer = utils.Importer('listener')
-        listener = importer.import_class_or_module(os.path.normpath(name))
-        if inspect.isclass(listener):
-            return self._instantiate_listener_class(listener, args)
-        if not args:
-            return listener
- raise DataError("Listeners implemented as modules do not take arguments")
-
-    def _instantiate_listener_class(self, listener, args):
-        try:
-            return listener(*args)
-        except:
-            raise DataError('Creating instance failed: %s\n%s'
-                            % utils.get_error_details())
+        return importer.import_class_or_module(os.path.normpath(name),
+                                               instantiate_with_args=args)

     def _get_version(self, listener):
         try:

==============================================================================
Revision: b2250eb66ea9
Author:   Pekka Klärck
Date:     Tue Feb 21 08:17:27 2012
Log: Implementation and tests for creating variable files as Python or Java classes.

Update issue 1021
Implementation and tests done. The main remaining task is documenting this in
the User Guide but review would be nice too.

The only surprise that I encountered was that static variable files
implemented as Java classes automatically created variables like ${toString}.
The logic we use to ignore such methods when creating Java libs is
somewhat complicated so I decided to use a simpler solution and ignore
all attributes in instances that are callable. That ought to be good enough
solution but needs to be documented.
http://code.google.com/p/robotframework/source/detail?r=b2250eb66ea9

Added:
 /atest/robot/variables/variable_file_implemented_as_class.txt
 /atest/testdata/variables/DynamicJavaClass.class
 /atest/testdata/variables/DynamicJavaClass.java
 /atest/testdata/variables/DynamicPythonClass.py
 /atest/testdata/variables/InvalidClass.py
 /atest/testdata/variables/JavaClass.class
 /atest/testdata/variables/JavaClass.java
 /atest/testdata/variables/PythonClass.py
 /atest/testdata/variables/variable_file_implemented_as_class.txt
Modified:
 /src/robot/variables/variables.py

=======================================
--- /dev/null
+++ /atest/robot/variables/variable_file_implemented_as_class.txt Tue Feb 21 08:17:27 2012
@@ -0,0 +1,32 @@
+*** Settings ***
+Suite Setup Run Tests ${EMPTY} variables/variable_file_implemented_as_class.txt
+Force Tags       regression
+Default Tags     pybot    jybot
+Resource         atest_resource.txt
+
+*** Test Cases ***
+
+Python Class
+    Check Test Case    ${TESTNAME}
+
+Methods in Python Class Do Not Create Variables
+    Check Test Case    ${TESTNAME}
+
+Dynamic Python Class
+    Check Test Case    ${TESTNAME}
+
+Java Class
+    [Tags]    jybot
+    Check Test Case    ${TESTNAME}
+
+Methods in Java Class Do Not Create Variables
+    [Tags]    jybot
+    Check Test Case    ${TESTNAME}
+
+Dynamic Java Class
+    [Tags]    jybot
+    Check Test Case    ${TESTNAME}
+
+Instantiating Fails
+    ${path} =    Normalize Path    ${DATADIR}/variables/InvalidClass.py
+ Check Syslog Contains Importing variable file '${path}' failed: Creating instance failed: TypeError:
=======================================
--- /dev/null
+++ /atest/testdata/variables/DynamicJavaClass.class Tue Feb 21 08:17:27 2012
@@ -0,0 +1,15 @@
+Êþº¾1+
+
+
+
+
+
+
+
+
+!"#$<init>()VCodeLineNumberTable getVariables5(Ljava/lang/String;Ljava/lang/String;)Ljava/util/Map; Signature[(Ljava/lang/String;Ljava/lang/String;)Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>; +SourceFileDynamicJavaClass.java java/util/HashMapjava/lang/Stringdynamic java stringjava/lang/StringBuilder %& '( )*LIST__dynamic java listDynamicJavaClassjava/lang/Objectappend-(Ljava/lang/String;)Ljava/lang/StringBuilder;toString()Ljava/lang/String;put8(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;!
+
+*·±g?»Y·N½Y+SY,S:-»Y·+¶ ¶,¶¶
+¶W-¶W-° 4
+=
=======================================
--- /dev/null
+++ /atest/testdata/variables/DynamicJavaClass.java     Tue Feb 21 08:17:27 2012
@@ -0,0 +1,13 @@
+import java.util.Map;
+import java.util.HashMap;
+
+public class DynamicJavaClass {
+
+    public Map<String, Object> getVariables(String arg1, String arg2) {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        String[] array = {arg1, arg2};
+        vars.put("dynamic java string", arg1 + " " + arg2);
+        vars.put("LIST__dynamic java list", array);
+        return vars;
+    }
+}
=======================================
--- /dev/null
+++ /atest/testdata/variables/DynamicPythonClass.py     Tue Feb 21 08:17:27 2012
@@ -0,0 +1,5 @@
+class DynamicPythonClass(object):
+
+    def get_variables(self, *args):
+        return {'dynamic_python_string': ' '.join(args),
+                'LIST__dynamic_python_list': args}
=======================================
--- /dev/null
+++ /atest/testdata/variables/InvalidClass.py   Tue Feb 21 08:17:27 2012
@@ -0,0 +1,4 @@
+class InvalidClass:
+
+    def __init__(self, i, get, no, args):
+        pass
=======================================
--- /dev/null
+++ /atest/testdata/variables/JavaClass.class   Tue Feb 21 08:17:27 2012
@@ -0,0 +1,18 @@
+Êþº¾1%
+   
+
+       
+
+
+ !      
+"#$
+javaStringLjava/lang/String;javaIntegerILIST__javaList[Ljava/lang/String;<init>()VCodeLineNumberTable
+javaMethod<clinit>
+SourceFileJavaClass.javahi
+java/lang/Stringxyz JavaClassjava/lang/Object!
+        
+    *
+*·*µ±        ±
+8
+³½YSYSYS³     ±
+
=======================================
--- /dev/null
+++ /atest/testdata/variables/JavaClass.java    Tue Feb 21 08:17:27 2012
@@ -0,0 +1,11 @@
+public class JavaClass {
+    public static String javaString = "hi";
+    public int javaInteger;
+    public static String[] LIST__javaList = {"x", "y", "z"};
+
+    public JavaClass() {
+        javaInteger = -1;
+    }
+
+    public void javaMethod() {}
+}
=======================================
--- /dev/null
+++ /atest/testdata/variables/PythonClass.py    Tue Feb 21 08:17:27 2012
@@ -0,0 +1,10 @@
+class PythonClass:
+    python_string = 'hello'
+    python_integer = None
+    LIST__python_list = ['a', 'b', 'c']
+
+    def __init__(self):
+        self.python_integer = 42
+
+    def python_method(self):
+        pass
=======================================
--- /dev/null
+++ /atest/testdata/variables/variable_file_implemented_as_class.txt Tue Feb 21 08:17:27 2012
@@ -0,0 +1,34 @@
+*** Settings ***
+Variables    PythonClass.py
+Variables    DynamicPythonClass.py    hello    world
+Variables    JavaClass.java
+Variables    DynamicJavaClass.class    hi    tellus
+Variables    InvalidClass.py
+
+*** Test Cases ***
+Python Class
+    Should Be Equal    ${PYTHON STRING}    hello
+    Should Be Equal    ${PYTHON INTEGER}    ${42}
+    Should Be True    ${PYTHON LIST} == ['a', 'b', 'c']
+
+Methods in Python Class Do Not Create Variables
+    Variable Should Not Exist    ${python_method}
+
+Dynamic Python Class
+    Should Be Equal    ${DYNAMIC PYTHON STRING}    hello world
+    Should Be True    ${DYNAMIC PYTHON LIST} == ['hello', 'world']
+
+Java Class
+    Should Be Equal    ${JAVA STRING}    hi
+    Should Be Equal    ${JAVA INTEGER}    ${-1}
+    Should Be True    ${JAVA LIST} == ['x', 'y', 'z']
+
+Methods in Java Class Do Not Create Variables
+    Variable Should Not Exist    ${javaMethod}
+    Variable Should Not Exist    ${equals}
+    Variable Should Not Exist    ${toString}
+    Variable Should Not Exist    ${class}
+
+Dynamic Java Class
+    Should Be Equal    ${DYNAMIC JAVA STRING}    hi tellus
+    Should Be True    ${DYNAMIC JAVA LIST} == ['hi', 'tellus']
=======================================
--- /src/robot/variables/variables.py   Wed Feb 15 00:21:02 2012
+++ /src/robot/variables/variables.py   Tue Feb 21 08:17:27 2012
@@ -13,12 +13,13 @@
 #  limitations under the License.

 import re
-import os
+import inspect
+from functools import partial
 from UserDict import UserDict
-if os.name == 'java':
+try:
     from java.lang.System import getProperty as getJavaSystemProperty
     from java.util import Map
-else:
+except ImportError:
     getJavaSystemProperty = lambda name: None
     class Map: pass

@@ -26,8 +27,8 @@
 from robot.errors import DataError
 from robot.output import LOGGER

-from isvar import is_var, is_scalar_var
-from variablesplitter import VariableSplitter
+from .isvar import is_var, is_scalar_var
+from .variablesplitter import VariableSplitter


 class Variables(utils.NormalizedDict):
@@ -48,7 +49,8 @@
     def __init__(self, identifiers=('$','@','%','&','*')):
         utils.NormalizedDict.__init__(self, ignore=['_'])
         self._identifiers = identifiers
-        self._importer = utils.Importer('variable file')
+ importer = utils.Importer('variable file').import_class_or_module_by_path + self._import_variable_file = partial(importer, instantiate_with_args=())

     def __setitem__(self, name, value):
         self._validate_var_name(name)
@@ -229,10 +231,9 @@

     def set_from_file(self, path, args=None, overwrite=False):
LOGGER.info("Importing variable file '%s' with args %s" % (path, args))
-        args = args or []
-        module = self._importer.import_class_or_module_by_path(path)
+        var_file = self._import_variable_file(path)
         try:
-            variables = self._get_variables_from_module(module, args)
+            variables = self._get_variables_from_var_file(var_file, args)
             self._set_from_file(variables, overwrite, path)
         except:
amsg = 'with arguments %s ' % utils.seq2str2(args) if args else ''
@@ -297,19 +298,17 @@
         LOGGER.warn(msg + '.')
         return self.replace_list(value)

-    def _get_variables_from_module(self, module, args):
-        variables = self._get_dynamical_variables(module, args)
+    def _get_variables_from_var_file(self, var_file, args):
+        variables = self._get_dynamical_variables(var_file, args or ())
         if variables is not None:
             return variables
-        names = [attr for attr in dir(module) if not attr.startswith('_')]
-        if hasattr(module, '__all__'):
-            names = [name for name in names if name in module.__all__]
-        return [(name, getattr(module, name)) for name in names]
-
-    def _get_dynamical_variables(self, module, args):
-        get_variables = getattr(module, 'get_variables', None)
+        names = self._get_static_variable_names(var_file)
+        return self._get_static_variables(var_file, names)
+
+    def _get_dynamical_variables(self, var_file, args):
+        get_variables = getattr(var_file, 'get_variables', None)
         if not get_variables:
-            get_variables = getattr(module, 'getVariables', None)
+            get_variables = getattr(var_file, 'getVariables', None)
         if not get_variables:
             return None
         variables = get_variables(*args)
@@ -320,6 +319,18 @@
         raise DataError("Expected mapping but %s returned %s."
% (get_variables.__name__, type(variables).__name__))

+    def _get_static_variable_names(self, var_file):
+ names = [attr for attr in dir(var_file) if not attr.startswith('_')]
+        if hasattr(var_file, '__all__'):
+            names = [name for name in names if name in var_file.__all__]
+        return names
+
+    def _get_static_variables(self, var_file, names):
+        variables = [(name, getattr(var_file, name)) for name in names]
+        if not inspect.ismodule(var_file):
+            variables = [var for var in variables if not callable(var[1])]
+        return variables
+
     def has_key(self, key):
         try:
             self[key]

Reply via email to