Robot Framework
bodyrunner.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 from collections import OrderedDict
17 from contextlib import contextmanager
18 import re
19 import time
20 
21 from robot.errors import (BreakLoop, ContinueLoop, DataError, ExecutionFailed,
22  ExecutionFailures, ExecutionPassed, ExecutionStatus)
23 from robot.result import (For as ForResult, While as WhileResult, If as IfResult,
24  IfBranch as IfBranchResult, Try as TryResult,
25  TryBranch as TryBranchResult)
26 from robot.output import librarylogger as logger
27 from robot.utils import (cut_assign_value, frange, get_error_message, is_string,
28  is_list_like, is_number, plural_or_not as s, seq2str,
29  split_from_equals, type_name, Matcher, timestr_to_secs)
30 from robot.variables import is_dict_variable, evaluate_expression
31 
32 from .statusreporter import StatusReporter
33 
34 
35 DEFAULT_WHILE_LIMIT = 10_000
36 
37 
38 class BodyRunner:
39 
40  def __init__(self, context, run=True, templated=False):
41  self._context_context = context
42  self._run_run = run
43  self._templated_templated = templated
44 
45  def run(self, body):
46  errors = []
47  passed = None
48  for step in body:
49  try:
50  step.run(self._context_context, self._run_run, self._templated_templated)
51  except ExecutionPassed as exception:
52  exception.set_earlier_failures(errors)
53  passed = exception
54  self._run_run = False
55  except ExecutionFailed as exception:
56  errors.extend(exception.get_errors())
57  self._run_run = exception.can_continue(self._context_context, self._templated_templated)
58  if passed:
59  raise passed
60  if errors:
61  raise ExecutionFailures(errors)
62 
63 
65 
66  def __init__(self, context, run=True):
67  self._context_context = context
68  self._run_run = run
69 
70  def run(self, step, name=None):
71  context = self._context_context
72  runner = context.get_runner(name or step.name)
73  if context.dry_run:
74  return runner.dry_run(step, context)
75  return runner.run(step, context, self._run_run)
76 
77 
78 def ForRunner(context, flavor='IN', run=True, templated=False):
79  runners = {'IN': ForInRunner,
80  'IN RANGE': ForInRangeRunner,
81  'IN ZIP': ForInZipRunner,
82  'IN ENUMERATE': ForInEnumerateRunner}
83  runner = runners[flavor or 'IN']
84  return runner(context, run, templated)
85 
86 
88  flavor = 'IN'
89 
90  def __init__(self, context, run=True, templated=False):
91  self._context_context = context
92  self._run_run = run
93  self._templated_templated = templated
94 
95  def run(self, data):
96  error = None
97  run = False
98  if self._run_run:
99  if data.error:
100  error = DataError(data.error, syntax=True)
101  else:
102  run = True
103  result = ForResult(data.variables, data.flavor, data.values)
104  with StatusReporter(data, result, self._context_context, run) as status:
105  if run:
106  try:
107  values_for_rounds = self._get_values_for_rounds_get_values_for_rounds(data)
108  except DataError as err:
109  error = err
110  else:
111  if self._run_loop_run_loop(data, result, values_for_rounds):
112  return
113  status.pass_status = result.NOT_RUN
114  self._run_one_round_run_one_round(data, result, run=False)
115  if error:
116  raise error
117 
118  def _run_loop(self, data, result, values_for_rounds):
119  errors = []
120  executed = False
121  for values in values_for_rounds:
122  executed = True
123  try:
124  self._run_one_round_run_one_round(data, result, values)
125  except (BreakLoop, ContinueLoop) as ctrl:
126  if ctrl.earlier_failures:
127  errors.extend(ctrl.earlier_failures.get_errors())
128  if isinstance(ctrl, BreakLoop):
129  break
130  except ExecutionPassed as passed:
131  passed.set_earlier_failures(errors)
132  raise passed
133  except ExecutionFailed as failed:
134  errors.extend(failed.get_errors())
135  if not failed.can_continue(self._context_context, self._templated_templated):
136  break
137  if errors:
138  raise ExecutionFailures(errors)
139  return executed
140 
141  def _get_values_for_rounds(self, data):
142  if self._context_context.dry_run:
143  return [None]
144  values_per_round = len(data.variables)
145  if self._is_dict_iteration_is_dict_iteration(data.values):
146  values = self._resolve_dict_values_resolve_dict_values(data.values)
147  values = self._map_dict_values_to_rounds_map_dict_values_to_rounds(values, values_per_round)
148  else:
149  values = self._resolve_values_resolve_values(data.values)
150  values = self._map_values_to_rounds_map_values_to_rounds(values, values_per_round)
151  return values
152 
153  def _is_dict_iteration(self, values):
154  all_name_value = True
155  for item in values:
156  if is_dict_variable(item):
157  return True
158  if split_from_equals(item)[1] is None:
159  all_name_value = False
160  if all_name_value and values:
161  name, value = split_from_equals(values[0])
162  logger.warn(
163  f"FOR loop iteration over values that are all in 'name=value' "
164  f"format like '{values[0]}' is deprecated. In the future this syntax "
165  f"will mean iterating over names and values separately like "
166  f"when iterating over '&{{dict}} variables. Escape at least one "
167  f"of the values like '{name}\\={value}' to use normal FOR loop "
168  f"iteration and to disable this warning."
169  )
170  return False
171 
172  def _resolve_dict_values(self, values):
173  result = OrderedDict()
174  replace_scalar = self._context_context.variables.replace_scalar
175  for item in values:
176  if is_dict_variable(item):
177  result.update(replace_scalar(item))
178  else:
179  key, value = split_from_equals(item)
180  if value is None:
181  raise DataError(f"Invalid FOR loop value '{item}'. When iterating "
182  f"over dictionaries, values must be '&{{dict}}' "
183  f"variables or use 'key=value' syntax.", syntax=True)
184  try:
185  result[replace_scalar(key)] = replace_scalar(value)
186  except TypeError:
187  err = get_error_message()
188  raise DataError(f"Invalid dictionary item '{item}': {err}")
189  return result.items()
190 
191  def _map_dict_values_to_rounds(self, values, per_round):
192  if per_round > 2:
193  raise DataError(f'Number of FOR loop variables must be 1 or 2 when '
194  f'iterating over dictionaries, got {per_round}.',
195  syntax=True)
196  return values
197 
198  def _resolve_values(self, values):
199  return self._context_context.variables.replace_list(values)
200 
201  def _map_values_to_rounds(self, values, per_round):
202  count = len(values)
203  if count % per_round != 0:
204  self._raise_wrong_variable_count_raise_wrong_variable_count(per_round, count)
205  # Map list of values to list of lists containing values per round.
206  return (values[i:i+per_round] for i in range(0, count, per_round))
207 
208  def _raise_wrong_variable_count(self, variables, values):
209  raise DataError(f'Number of FOR loop values should be multiple of its '
210  f'variables. Got {variables} variables but {values} '
211  f'value{s(values)}.')
212 
213  def _run_one_round(self, data, result, values=None, run=True):
214  result = result.body.create_iteration()
215  if values is not None:
216  variables = self._context_context.variables
217  else: # Not really run (earlier failure, unexecuted IF branch, dry-run)
218  variables = {}
219  values = [''] * len(data.variables)
220  for name, value in self._map_variables_and_values_map_variables_and_values(data.variables, values):
221  variables[name] = value
222  result.variables[name] = cut_assign_value(value)
223  runner = BodyRunner(self._context_context, run, self._templated_templated)
224  with StatusReporter(data, result, self._context_context, run):
225  runner.run(data.body)
226 
227  def _map_variables_and_values(self, variables, values):
228  if len(variables) == 1 and len(values) != 1:
229  return [(variables[0], tuple(values))]
230  return zip(variables, values)
231 
232 
234  flavor = 'IN RANGE'
235 
236  def _resolve_dict_values(self, values):
237  raise DataError('FOR IN RANGE loops do not support iterating over '
238  'dictionaries.', syntax=True)
239 
240  def _map_values_to_rounds(self, values, per_round):
241  if not 1 <= len(values) <= 3:
242  raise DataError(f'FOR IN RANGE expected 1-3 values, got {len(values)}.',
243  syntax=True)
244  try:
245  values = [self._to_number_with_arithmetic_to_number_with_arithmetic(v) for v in values]
246  except Exception:
247  msg = get_error_message()
248  raise DataError(f'Converting FOR IN RANGE values failed: {msg}.')
249  values = frange(*values)
250  return ForInRunner._map_values_to_rounds(self, values, per_round)
251 
252  def _to_number_with_arithmetic(self, item):
253  if is_number(item):
254  return item
255  number = eval(str(item), {})
256  if not is_number(number):
257  raise TypeError(f'Expected number, got {type_name(item)}.')
258  return number
259 
260 
262  flavor = 'IN ZIP'
263 
266  _start = 0
267 
268  def _resolve_dict_values(self, values):
269  raise DataError('FOR IN ZIP loops do not support iterating over dictionaries.',
270  syntax=True)
271 
272  def _map_values_to_rounds(self, values, per_round):
273  for item in values:
274  if not is_list_like(item):
275  raise DataError(f"FOR IN ZIP items must all be list-like, "
276  f"got {type_name(item)} '{item}'.")
277  if len(values) % per_round != 0:
278  self._raise_wrong_variable_count_raise_wrong_variable_count(per_round, len(values))
279  return zip(*(list(item) for item in values))
280 
281 
283  flavor = 'IN ENUMERATE'
284 
285  def _is_dict_iteration(self, values):
286  if values and values[-1].startswith('start='):
287  values = values[:-1]
288  return super()._is_dict_iteration(values)
289 
290  def _resolve_dict_values(self, values):
291  self._start, values = self._get_start_get_start(values)
292  return ForInRunner._resolve_dict_values(self, values)
293 
294  def _resolve_values(self, values):
295  self._start, values = self._get_start_get_start(values)
296  return ForInRunner._resolve_values(self, values)
297 
298  def _get_start(self, values):
299  if not values[-1].startswith('start='):
300  return 0, values
301  *values, start = values
302  if not values:
303  raise DataError('FOR loop has no loop values.', syntax=True)
304  try:
305  start = self._context_context.variables.replace_string(start[6:])
306  try:
307  start = int(start)
308  except ValueError:
309  raise DataError(f"Start value must be an integer, got '{start}'.")
310  except DataError as err:
311  raise DataError(f'Invalid start value: {err}')
312  return start, values
313 
314  def _map_dict_values_to_rounds(self, values, per_round):
315  if per_round > 3:
316  raise DataError(f'Number of FOR IN ENUMERATE loop variables must be 1-3 '
317  f'when iterating over dictionaries, got {per_round}.',
318  syntax=True)
319  if per_round == 2:
320  return ((i, v) for i, v in enumerate(values, start=self._start))
321  return ((i,) + v for i, v in enumerate(values, start=self._start))
322 
323  def _map_values_to_rounds(self, values, per_round):
324  per_round = max(per_round-1, 1)
325  values = ForInRunner._map_values_to_rounds(self, values, per_round)
326  return ([i] + v for i, v in enumerate(values, start=self._start))
327 
328  def _raise_wrong_variable_count(self, variables, values):
329  raise DataError(f'Number of FOR IN ENUMERATE loop values should be multiple of '
330  f'its variables (excluding the index). Got {variables} '
331  f'variables but {values} value{s(values)}.')
332 
333 
335 
336  def __init__(self, context, run=True, templated=False):
337  self._context_context = context
338  self._run_run = run
339  self._templated_templated = templated
340 
341  def run(self, data):
342  ctx = self._context_context
343  error = None
344  run = False
345  limit = None
346  if self._run_run:
347  if data.error:
348  error = DataError(data.error, syntax=True)
349  elif not ctx.dry_run:
350  try:
351  limit = WhileLimit.create(data.limit, ctx.variables)
352  run = self._should_run_should_run(data.condition, ctx.variables)
353  except DataError as err:
354  error = err
355  result = WhileResult(data.condition, data.limit)
356  with StatusReporter(data, result, self._context_context, run):
357  if ctx.dry_run or not run:
358  self._run_iteration_run_iteration(data, result, run)
359  if error:
360  raise error
361  return
362  errors = []
363  while True:
364  try:
365  with limit:
366  self._run_iteration_run_iteration(data, result)
367  except (BreakLoop, ContinueLoop) as ctrl:
368  if ctrl.earlier_failures:
369  errors.extend(ctrl.earlier_failures.get_errors())
370  if isinstance(ctrl, BreakLoop):
371  break
372  except ExecutionPassed as passed:
373  passed.set_earlier_failures(errors)
374  raise passed
375  except ExecutionFailed as failed:
376  errors.extend(failed.get_errors())
377  if not failed.can_continue(ctx, self._templated_templated):
378  break
379  if not self._should_run_should_run(data.condition, ctx.variables):
380  break
381  if errors:
382  raise ExecutionFailures(errors)
383 
384  def _run_iteration(self, data, result, run=True):
385  runner = BodyRunner(self._context_context, run, self._templated_templated)
386  with StatusReporter(data, result.body.create_iteration(), self._context_context, run):
387  runner.run(data.body)
388 
389  def _should_run(self, condition, variables):
390  try:
391  condition = variables.replace_scalar(condition)
392  if is_string(condition):
393  return evaluate_expression(condition, variables.current.store)
394  return bool(condition)
395  except Exception:
396  msg = get_error_message()
397  raise DataError(f'Evaluating WHILE condition failed: {msg}')
398 
399 
400 class IfRunner:
401 
404  _dry_run_stack = []
405 
406  def __init__(self, context, run=True, templated=False):
407  self._context_context = context
408  self._run_run = run
409  self._templated_templated = templated
410 
411  def run(self, data):
412  with self._dry_run_recursion_detection_dry_run_recursion_detection(data) as recursive_dry_run:
413  error = None
414  with StatusReporter(data, IfResult(), self._context_context, self._run_run):
415  for branch in data.body:
416  try:
417  if self._run_if_branch_run_if_branch(branch, recursive_dry_run, data.error):
418  self._run_run = False
419  except ExecutionStatus as err:
420  error = err
421  self._run_run = False
422  if error:
423  raise error
424 
425  @contextmanager
427  dry_run = self._context_context.dry_run
428  if dry_run:
429  recursive_dry_run = data in self._dry_run_stack_dry_run_stack
430  self._dry_run_stack_dry_run_stack.append(data)
431  else:
432  recursive_dry_run = False
433  try:
434  yield recursive_dry_run
435  finally:
436  if dry_run:
437  self._dry_run_stack_dry_run_stack.pop()
438 
439  def _run_if_branch(self, branch, recursive_dry_run=False, syntax_error=None):
440  context = self._context_context
441  result = IfBranchResult(branch.type, branch.condition)
442  error = None
443  if syntax_error:
444  run_branch = False
445  error = DataError(syntax_error, syntax=True)
446  else:
447  try:
448  run_branch = self._should_run_branch_should_run_branch(branch, context, recursive_dry_run)
449  except DataError as err:
450  error = err
451  run_branch = False
452  with StatusReporter(branch, result, context, run_branch):
453  runner = BodyRunner(context, run_branch, self._templated_templated)
454  if not recursive_dry_run:
455  runner.run(branch.body)
456  if error and self._run_run:
457  raise error
458  return run_branch
459 
460  def _should_run_branch(self, branch, context, recursive_dry_run=False):
461  condition = branch.condition
462  variables = context.variables
463  if context.dry_run:
464  return not recursive_dry_run
465  if not self._run_run:
466  return False
467  if condition is None:
468  return True
469  try:
470  condition = variables.replace_scalar(condition)
471  if is_string(condition):
472  return evaluate_expression(condition, variables.current.store)
473  return bool(condition)
474  except Exception:
475  msg = get_error_message()
476  raise DataError(f'Evaluating {branch.type} condition failed: {msg}')
477 
478 
479 class TryRunner:
480 
481  def __init__(self, context, run=True, templated=False):
482  self._context_context = context
483  self._run_run = run
484  self._templated_templated = templated
485 
486  def run(self, data):
487  run = self._run_run
488  with StatusReporter(data, TryResult(), self._context_context, run):
489  if data.error:
490  self._run_invalid_run_invalid(data)
491  return
492  error = self._run_try_run_try(data, run)
493  run_excepts_or_else = self._should_run_excepts_or_else_should_run_excepts_or_else(error, run)
494  if error:
495  error = self._run_excepts_run_excepts(data, error, run=run_excepts_or_else)
496  self._run_else_run_else(data, run=False)
497  else:
498  self._run_excepts_run_excepts(data, error, run=False)
499  error = self._run_else_run_else(data, run=run_excepts_or_else)
500  error = self._run_finally_run_finally(data, run) or error
501  if error:
502  raise error
503 
504  def _run_invalid(self, data):
505  error_reported = False
506  for branch in data.body:
507  result = TryBranchResult(branch.type, branch.patterns, branch.variable)
508  with StatusReporter(branch, result, self._context_context, run=False, suppress=True):
509  runner = BodyRunner(self._context_context, run=False, templated=self._templated_templated)
510  runner.run(branch.body)
511  if not error_reported:
512  error_reported = True
513  raise DataError(data.error, syntax=True)
514  raise ExecutionFailed(data.error, syntax=True)
515 
516  def _run_try(self, data, run):
517  result = TryBranchResult(data.TRY)
518  return self._run_branch_run_branch(data.try_branch, result, run)
519 
520  def _should_run_excepts_or_else(self, error, run):
521  if not run:
522  return False
523  if not error:
524  return True
525  return not (error.skip or error.syntax or isinstance(error, ExecutionPassed))
526 
527  def _run_branch(self, branch, result, run=True, error=None):
528  try:
529  with StatusReporter(branch, result, self._context_context, run):
530  if error:
531  raise error
532  runner = BodyRunner(self._context_context, run, self._templated_templated)
533  runner.run(branch.body)
534  except ExecutionStatus as err:
535  return err
536  else:
537  return None
538 
539  def _run_excepts(self, data, error, run):
540  for branch in data.except_branches:
541  try:
542  run_branch = run and self._should_run_except_should_run_except(branch, error)
543  except DataError as err:
544  run_branch = True
545  pattern_error = err
546  else:
547  pattern_error = None
548  result = TryBranchResult(branch.type, branch.patterns,
549  branch.pattern_type, branch.variable)
550  if run_branch:
551  if branch.variable:
552  self._context_context.variables[branch.variable] = str(error)
553  error = self._run_branch_run_branch(branch, result, error=pattern_error)
554  run = False
555  else:
556  self._run_branch_run_branch(branch, result, run=False)
557  return error
558 
559  def _should_run_except(self, branch, error):
560  if not branch.patterns:
561  return True
562  matchers = {
563  'GLOB': lambda m, p: Matcher(p, spaceless=False, caseless=False).match(m),
564  'LITERAL': lambda m, p: m == p,
565  'REGEXP': lambda m, p: re.match(rf'{p}\Z', m) is not None,
566  'START': lambda m, p: m.startswith(p)
567  }
568  if branch.pattern_type:
569  pattern_type = self._context_context.variables.replace_string(branch.pattern_type)
570  else:
571  pattern_type = 'LITERAL'
572  matcher = matchers.get(pattern_type.upper())
573  if not matcher:
574  raise DataError(f"Invalid EXCEPT pattern type '{pattern_type}', "
575  f"expected {seq2str(matchers, lastsep=' or ')}.")
576  for pattern in branch.patterns:
577  if matcher(error.message, self._context_context.variables.replace_string(pattern)):
578  return True
579  return False
580 
581  def _run_else(self, data, run):
582  if data.else_branch:
583  result = TryBranchResult(data.ELSE)
584  return self._run_branch_run_branch(data.else_branch, result, run)
585 
586  def _run_finally(self, data, run):
587  if data.finally_branch:
588  result = TryBranchResult(data.FINALLY)
589  try:
590  with StatusReporter(data.finally_branch, result, self._context_context, run):
591  runner = BodyRunner(self._context_context, run, self._templated_templated)
592  runner.run(data.finally_branch.body)
593  except ExecutionStatus as err:
594  return err
595  else:
596  return None
597 
598 
600 
601  @classmethod
602  def create(cls, limit, variables):
603  if not limit:
604  return IterationCountLimit(DEFAULT_WHILE_LIMIT)
605  value = variables.replace_string(limit)
606  if value.upper() == 'NONE':
607  return NoLimit()
608  try:
609  count = int(value.replace(' ', ''))
610  except ValueError:
611  pass
612  else:
613  if count <= 0:
614  raise DataError(f"Invalid WHILE loop limit: Iteration count must be "
615  f"a positive integer, got '{count}'.")
616  return IterationCountLimit(count)
617  try:
618  secs = timestr_to_secs(value)
619  except ValueError as err:
620  raise DataError(f'Invalid WHILE loop limit: {err.args[0]}')
621  else:
622  return DurationLimit(secs)
623 
624  def limit_exceeded(self):
625  raise ExecutionFailed(f"WHILE loop was aborted because it did not finish "
626  f"within the limit of {self}. Use the 'limit' argument "
627  f"to increase or remove the limit if needed.")
628 
629  def __enter__(self):
630  raise NotImplementedError
631 
632  def __exit__(self, exc_type, exc_val, exc_tb):
633  return None
634 
635 
637 
638  def __init__(self, max_time):
639  self.max_timemax_time = max_time
640  self.start_timestart_time = None
641 
642  def __enter__(self):
643  if not self.start_timestart_time:
644  self.start_timestart_time = time.time()
645  if time.time() - self.start_timestart_time > self.max_timemax_time:
646  self.limit_exceededlimit_exceeded()
647 
648  def __str__(self):
649  return f'{self.max_time} seconds'
650 
651 
653 
654  def __init__(self, max_iterations):
655  self.max_iterationsmax_iterations = max_iterations
656  self.current_iterationscurrent_iterations = 0
657 
658  def __enter__(self):
659  if self.current_iterationscurrent_iterations >= self.max_iterationsmax_iterations:
660  self.limit_exceededlimit_exceeded()
661  self.current_iterationscurrent_iterations += 1
662 
663  def __str__(self):
664  return f'{self.max_iterations} iterations'
665 
666 
668 
669  def __enter__(self):
670  pass
Used for communicating failures in test execution.
Definition: errors.py:178
def __init__(self, context, run=True, templated=False)
Definition: bodyrunner.py:40
def _map_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:323
def _raise_wrong_variable_count(self, variables, values)
Definition: bodyrunner.py:328
def _map_dict_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:314
def _map_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:240
def _is_dict_iteration(self, values)
Definition: bodyrunner.py:153
def _raise_wrong_variable_count(self, variables, values)
Definition: bodyrunner.py:208
def _map_variables_and_values(self, variables, values)
Definition: bodyrunner.py:227
def _run_one_round(self, data, result, values=None, run=True)
Definition: bodyrunner.py:213
def _map_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:201
def _resolve_dict_values(self, values)
Definition: bodyrunner.py:172
def _map_dict_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:191
def _run_loop(self, data, result, values_for_rounds)
Definition: bodyrunner.py:118
def __init__(self, context, run=True, templated=False)
Definition: bodyrunner.py:90
def _map_values_to_rounds(self, values, per_round)
Definition: bodyrunner.py:272
def _should_run_branch(self, branch, context, recursive_dry_run=False)
Definition: bodyrunner.py:460
def __init__(self, context, run=True, templated=False)
Definition: bodyrunner.py:406
def _run_if_branch(self, branch, recursive_dry_run=False, syntax_error=None)
Definition: bodyrunner.py:439
def _dry_run_recursion_detection(self, data)
Definition: bodyrunner.py:426
def __init__(self, context, run=True)
Definition: bodyrunner.py:66
def run(self, step, name=None)
Definition: bodyrunner.py:70
def _run_branch(self, branch, result, run=True, error=None)
Definition: bodyrunner.py:527
def _run_finally(self, data, run)
Definition: bodyrunner.py:586
def _run_excepts(self, data, error, run)
Definition: bodyrunner.py:539
def _should_run_excepts_or_else(self, error, run)
Definition: bodyrunner.py:520
def _run_else(self, data, run)
Definition: bodyrunner.py:581
def _should_run_except(self, branch, error)
Definition: bodyrunner.py:559
def __init__(self, context, run=True, templated=False)
Definition: bodyrunner.py:481
def _run_try(self, data, run)
Definition: bodyrunner.py:516
def __exit__(self, exc_type, exc_val, exc_tb)
Definition: bodyrunner.py:632
def create(cls, limit, variables)
Definition: bodyrunner.py:602
def _should_run(self, condition, variables)
Definition: bodyrunner.py:389
def __init__(self, context, run=True, templated=False)
Definition: bodyrunner.py:336
def _run_iteration(self, data, result, run=True)
Definition: bodyrunner.py:384
def ForRunner(context, flavor='IN', run=True, templated=False)
Definition: bodyrunner.py:78
def get_error_message()
Returns error message of the last occurred exception.
Definition: error.py:34
def split_from_equals(string)
Definition: escaping.py:105
def frange(*args)
Like range() but accepts float arguments.
Definition: frange.py:20
def timestr_to_secs(timestr, round_to=3, accept_plain_values=True)
Parses time strings like '1h 10s', '01:00:10' and '42' and returns seconds.
Definition: robottime.py:55
def is_list_like(item)
Definition: robottypes.py:66
def cut_assign_value(value)
Definition: text.py:104
def evaluate_expression(expression, variable_store, modules=None, namespace=None)
Definition: evaluation.py:31
def is_dict_variable(string)
Definition: search.py:46