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> ()V Code LineNumberTable getVariables 5(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;>;
+SourceFile DynamicJavaClass.java java/util/HashMap java/lang/String dynamic
java
string java/lang/StringBuilder % & ' ( ) * LIST__dynamic
java
list DynamicJavaClass java/lang/Object append -(Ljava/lang/String;)Ljava/lang/StringBuilder; toString ()Ljava/lang/String; put 8(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 %
+
+
+
+
+
+ !
+ " # $
+javaString Ljava/lang/String; javaInteger I LIST__javaList [Ljava/lang/String; <init> ()V Code LineNumberTable
+javaMethod <clinit>
+SourceFile JavaClass.java hi
+ java/lang/String x y z JavaClass java/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]