Robot Framework Integrated Development Environment (RIDE)
Remote.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 __future__ import absolute_import
17 
18 try:
19  import httplib
20  import xmlrpclib
21 except ImportError: # Py3
22  import http.client as httplib
23  import xmlrpc.client as xmlrpclib
24 import re
25 import socket
26 import sys
27 import time
28 
29 try:
30  from xml.parsers.expat import ExpatError
31 except ImportError: # No expat in IronPython 2.7
33  pass
34 
35 from robotide.lib.robot.errors import RemoteError
36 from robotide.lib.robot.utils import (is_bytes, is_dict_like, is_list_like, is_number,
37  is_string, timestr_to_secs, unic, DotDict,
38  IRONPYTHON, JYTHON, PY2)
39 
40 
41 class Remote():
42  ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
43 
44 
58  def __init__(self, uri='http://127.0.0.1:8270', timeout=None):
59  if '://' not in uri:
60  uri = 'http://' + uri
61  if timeout:
62  timeout = timestr_to_secs(timeout)
63  self._uri_uri = uri
64  self._client_client = XmlRpcRemoteClient(uri, timeout)
65 
66  def get_keyword_names(self, attempts=2):
67  for i in range(attempts):
68  time.sleep(i)
69  try:
70  return self._client_client.get_keyword_names()
71  except TypeError as err:
72  error = err
73  raise RuntimeError('Connecting remote server at %s failed: %s'
74  % (self._uri_uri, error))
75 
76  def get_keyword_arguments(self, name):
77  try:
78  return self._client_client.get_keyword_arguments(name)
79  except TypeError:
80  return ['*args']
81 
82  def get_keyword_types(self, name):
83  try:
84  return self._client_client.get_keyword_types(name)
85  except TypeError:
86  return None
87 
88  def get_keyword_tags(self, name):
89  try:
90  return self._client_client.get_keyword_tags(name)
91  except TypeError:
92  return None
93 
94  def get_keyword_documentation(self, name):
95  try:
96  return self._client_client.get_keyword_documentation(name)
97  except TypeError:
98  return None
99 
100  def run_keyword(self, name, args, kwargs):
101  coercer = ArgumentCoercer()
102  args = coercer.coerce(args)
103  kwargs = coercer.coerce(kwargs)
104  result = RemoteResult(self._client_client.run_keyword(name, args, kwargs))
105  sys.stdout.write(result.output)
106  if result.status != 'PASS':
107  raise RemoteError(result.error, result.traceback, result.fatal,
108  result.continuable)
109  return result.return_
110 
111 
113  binary = re.compile('[\x00-\x08\x0B\x0C\x0E-\x1F]')
114  non_ascii = re.compile('[\x80-\xff]')
115 
116  def coerce(self, argument):
117  for handles, handler in [(is_string, self._handle_string_handle_string),
118  (is_bytes, self._handle_bytes_handle_bytes),
119  (is_number, self._pass_through_pass_through),
120  (is_dict_like, self._coerce_dict_coerce_dict),
121  (is_list_like, self._coerce_list_coerce_list),
122  (lambda arg: True, self._to_string_to_string)]:
123  if handles(argument):
124  return handler(argument)
125 
126  def _handle_string(self, arg):
127  if self._string_contains_binary_string_contains_binary(arg):
128  return self._handle_binary_in_string_handle_binary_in_string(arg)
129  return arg
130 
131  def _string_contains_binary(self, arg):
132  return (self.binarybinary.search(arg) or
133  is_bytes(arg) and self.non_asciinon_ascii.search(arg))
134 
135  def _handle_binary_in_string(self, arg):
136  try:
137  if not is_bytes(arg):
138  arg = arg.encode('ASCII')
139  except UnicodeError:
140  raise ValueError('Cannot represent %r as binary.' % arg)
141  return xmlrpclib.Binary(arg)
142 
143  def _handle_bytes(self, arg):
144  # http://bugs.jython.org/issue2429
145  if IRONPYTHON or JYTHON:
146  arg = str(arg)
147  return xmlrpclib.Binary(arg)
148 
149  def _pass_through(self, arg):
150  return arg
151 
152  def _coerce_list(self, arg):
153  return [self.coercecoerce(item) for item in arg]
154 
155  def _coerce_dict(self, arg):
156  return dict((self._to_key_to_key(key), self.coercecoerce(arg[key])) for key in arg)
157 
158  def _to_key(self, item):
159  item = self._to_string_to_string(item)
160  self._validate_key_validate_key(item)
161  return item
162 
163  def _to_string(self, item):
164  item = unic(item) if item is not None else ''
165  return self._handle_string_handle_string(item)
166 
167  def _validate_key(self, key):
168  if isinstance(key, xmlrpclib.Binary):
169  raise ValueError('Dictionary keys cannot be binary. Got %s%r.'
170  % ('b' if PY2 else '', key.data))
171  if IRONPYTHON:
172  try:
173  key.encode('ASCII')
174  except UnicodeError:
175  raise ValueError('Dictionary keys cannot contain non-ASCII '
176  'characters on IronPython. Got %r.' % key)
177 
178 
179 class RemoteResult():
180 
181  def __init__(self, result):
182  if not (is_dict_like(result) and 'status' in result):
183  raise RuntimeError('Invalid remote result dictionary: %s' % result)
184  self.statusstatus = result['status']
185  self.outputoutput = unic(self._get_get(result, 'output'))
186  self.return_return_ = self._get_get(result, 'return')
187  self.errorerror = unic(self._get_get(result, 'error'))
188  self.tracebacktraceback = unic(self._get_get(result, 'traceback'))
189  self.fatalfatal = bool(self._get_get(result, 'fatal', False))
190  self.continuablecontinuable = bool(self._get_get(result, 'continuable', False))
191 
192  def _get(self, result, key, default=''):
193  value = result.get(key, default)
194  return self._convert_convert(value)
195 
196  def _convert(self, value):
197  if isinstance(value, xmlrpclib.Binary):
198  return bytes(value.data)
199  if is_dict_like(value):
200  return DotDict((k, self._convert_convert(v)) for k, v in value.items())
201  if is_list_like(value):
202  return [self._convert_convert(v) for v in value]
203  return value
204 
205 
207 
208  def __init__(self, uri, timeout=None):
209  if uri.startswith('https://'):
210  transport = TimeoutHTTPSTransport(timeout=timeout)
211  else:
212  transport = TimeoutHTTPTransport(timeout=timeout)
213  self._server_server = xmlrpclib.ServerProxy(uri, encoding='UTF-8',
214  transport=transport)
215 
216  def get_keyword_names(self):
217  try:
218  return self._server_server.get_keyword_names()
219  except (socket.error, xmlrpclib.Error) as err:
220  raise TypeError(err)
221 
222  def get_keyword_arguments(self, name):
223  try:
224  return self._server_server.get_keyword_arguments(name)
225  except xmlrpclib.Error:
226  raise TypeError
227 
228  def get_keyword_types(self, name):
229  try:
230  return self._server_server.get_keyword_types(name)
231  except xmlrpclib.Error:
232  raise TypeError
233 
234  def get_keyword_tags(self, name):
235  try:
236  return self._server_server.get_keyword_tags(name)
237  except xmlrpclib.Error:
238  raise TypeError
239 
240  def get_keyword_documentation(self, name):
241  try:
242  return self._server_server.get_keyword_documentation(name)
243  except xmlrpclib.Error:
244  raise TypeError
245 
246  def run_keyword(self, name, args, kwargs):
247  run_keyword_args = [name, args, kwargs] if kwargs else [name, args]
248  try:
249  return self._server_server.run_keyword(*run_keyword_args)
250  except xmlrpclib.Fault as err:
251  message = err.faultString
252  except socket.error as err:
253  message = 'Connection to remote server broken: %s' % err
254  except ExpatError as err:
255  message = ('Processing XML-RPC return value failed. '
256  'Most often this happens when the return value '
257  'contains characters that are not valid in XML. '
258  'Original error was: ExpatError: %s' % err)
259  raise RuntimeError(message)
260 
261 
262 # Custom XML-RPC timeouts based on
263 # http://stackoverflow.com/questions/2425799/timeout-for-xmlrpclib-client-requests
264 
265 class TimeoutHTTPTransport(xmlrpclib.Transport):
266 
269  _connection_class = httplib.HTTPConnection
270 
271  def __init__(self, use_datetime=0, timeout=None):
272  xmlrpclib.Transport.__init__(self, use_datetime)
273  if not timeout:
274  timeout = socket._GLOBAL_DEFAULT_TIMEOUT
275  self.timeouttimeout = timeout
276 
277  def make_connection(self, host):
278  if self._connection_connection and host == self._connection_connection[0]:
279  return self._connection_connection[1]
280  chost, self._extra_headers, x509 = self.get_host_info(host)
281  self._connection_connection = host, self._connection_class_connection_class(chost, timeout=self.timeouttimeout)
282  return self._connection_connection[1]
283 
284 
285 if IRONPYTHON:
286 
287  class TimeoutHTTPTransport(xmlrpclib.Transport):
288 
289  def __init__(self, use_datetime=0, timeout=None):
290  xmlrpclib.Transport.__init__(self, use_datetime)
291  if timeout:
292  raise RuntimeError('Timeouts are not supported on IronPython.')
293 
294 
296 
299  _connection_class = httplib.HTTPSConnection
Used by Remote library to report remote errors.
Definition: errors.py:324
def _get(self, result, key, default='')
Definition: Remote.py:192
def get_keyword_names(self, attempts=2)
Definition: Remote.py:66
def run_keyword(self, name, args, kwargs)
Definition: Remote.py:100
def __init__(self, uri='http://127.0.0.1:8270', timeout=None)
Connects to a remote server at uri.
Definition: Remote.py:58
def __init__(self, use_datetime=0, timeout=None)
Definition: Remote.py:271
def run_keyword(self, name, args, kwargs)
Definition: Remote.py:246
def timestr_to_secs(timestr, round_to=3)
Parses time like '1h 10s', '01:00:10' or '42' and returns seconds.
Definition: robottime.py:45