44import irods .exception as ex
55from io import open as io_open
66from irods .message import Message , StringProperty
7+ import six
78
89class RemoveRuleMessage (Message ):
910 #define RULE_EXEC_DEL_INP_PI "str ruleExecId[NAME_LEN];"
@@ -20,8 +21,11 @@ def __init__(self, session, rule_file=None, body='', params=None, output='', ins
2021
2122 Arguments:
2223 Use one of:
23- * rule_file : the name of an existing rule script ending in '.r' and containing iRODS rules
24- * body: the text of the rule code or rule call(s) to be run.
24+ * rule_file : the name of an existing file containint "rule script" style code. In the context of
25+ the native iRODS Rule Language, this is a file ending in '.r' and containing iRODS rules.
26+ Optionally, this parameter can be a file-like object containing the rule script text.
27+ * body: the text of block of rule code (possibly including rule calls) to be run as if it were
28+ the body of a rule, e.g. the part between the braces of a rule definition in the iRODS rule language.
2529 * instance_name: the name of the rule engine instance in the context of which to run the rule(s).
2630 * output may be set to 'ruleExecOut' if console output is expected on stderr or stdout streams.
2731 * params are key/value pairs to be sent into a rule_file.
@@ -62,11 +66,37 @@ def remove_by_id(self,*ids):
6266 raise RuntimeError ("Error removing rule {id_}" .format (** locals ()))
6367
6468 def load (self , rule_file , encoding = 'utf-8' ):
69+ """Load rule code with rule-file (*.r) semantics.
70+
71+ A "main" rule is defined first; name does not matter. Other rules may follow, which will be
72+ callable from the first rule. Any rules defined in active rule-bases within the server are
73+ also callable.
74+
75+ The `rule_file' parameter is a filename or file-like object. We give it either:
76+ - a string holding the path to a rule-file in the local filesystem, or
77+ - an in-memory object (eg. io.StringIO or io.BytesIO) whose content is that of a rule-file.
78+
79+ This addresses a regression in v1.1.0; see issue #336. In v1.1.1+, if rule code is passed in literally via
80+ the `body' parameter of the Rule constructor, it is interpreted as if it were the body of a rule, and
81+ therefore it may not contain internal rule definitions. However, if rule code is submitted as the content
82+ of a file or file-like object referred to by the `rule_file' parameter of the Rule constructor, will be
83+ interpreted as .r-file content. Therefore, it must contain a main rule definition first, followed
84+ possibly by others which will be callable from the main rule as if they were part of the core rule-base.
85+
86+ """
6587 self .body = '@external\n '
6688
67- # parse rule file
68- with io_open (rule_file , encoding = encoding ) as f :
89+
90+ with (io_open (rule_file , encoding = encoding ) if isinstance (rule_file ,six .string_types ) else rule_file
91+ ) as f :
92+
93+ # parse rule file line-by-line
6994 for line in f :
95+
96+ # convert input line to Unicode if necessary
97+ if isinstance (line , bytes ):
98+ line = line .decode (encoding )
99+
70100 # parse input line
71101 if line .strip ().lower ().startswith ('input' ):
72102
0 commit comments