Robot Framework
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 contextlib import contextmanager
17 
18 import http.client
19 import re
20 import socket
21 import sys
22 import xmlrpc.client
23 from xml.parsers.expat import ExpatError
24 
25 from robot.errors import RemoteError
26 from robot.utils import (DotDict, is_bytes, is_dict_like, is_list_like, is_number,
27  is_string, safe_str, timestr_to_secs)
28 
29 
30 class Remote:
31  ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
32 
33 
45  def __init__(self, uri='http://127.0.0.1:8270', timeout=None):
46  if '://' not in uri:
47  uri = 'http://' + uri
48  if timeout:
49  timeout = timestr_to_secs(timeout)
50  self._uri_uri = uri
51  self._client_client = XmlRpcRemoteClient(uri, timeout)
52  self._lib_info_lib_info = None
53  self._lib_info_initialized_lib_info_initialized = False
54 
55  def get_keyword_names(self):
56  if self._is_lib_info_available_is_lib_info_available():
57  return [name for name in self._lib_info_lib_info
58  if not (name[:2] == '__' and name[-2:] == '__')]
59  try:
60  return self._client_client.get_keyword_names()
61  except TypeError as error:
62  raise RuntimeError('Connecting remote server at %s failed: %s'
63  % (self._uri_uri, error))
64 
66  if not self._lib_info_initialized_lib_info_initialized:
67  try:
68  self._lib_info_lib_info = self._client_client.get_library_information()
69  except TypeError:
70  pass
71  self._lib_info_initialized_lib_info_initialized = True
72  return self._lib_info_lib_info is not None
73 
74  def get_keyword_arguments(self, name):
75  return self._get_kw_info_get_kw_info(name, 'args', self._client_client.get_keyword_arguments,
76  default=['*args'])
77 
78  def _get_kw_info(self, kw, info, getter, default=None):
79  if self._is_lib_info_available_is_lib_info_available():
80  return self._lib_info_lib_info[kw].get(info, default)
81  try:
82  return getter(kw)
83  except TypeError:
84  return default
85 
86  def get_keyword_types(self, name):
87  return self._get_kw_info_get_kw_info(name, 'types', self._client_client.get_keyword_types,
88  default=())
89 
90  def get_keyword_tags(self, name):
91  return self._get_kw_info_get_kw_info(name, 'tags', self._client_client.get_keyword_tags)
92 
93  def get_keyword_documentation(self, name):
94  return self._get_kw_info_get_kw_info(name, 'doc', self._client_client.get_keyword_documentation)
95 
96  def run_keyword(self, name, args, kwargs):
97  coercer = ArgumentCoercer()
98  args = coercer.coerce(args)
99  kwargs = coercer.coerce(kwargs)
100  result = RemoteResult(self._client_client.run_keyword(name, args, kwargs))
101  sys.stdout.write(result.output)
102  if result.status != 'PASS':
103  raise RemoteError(result.error, result.traceback, result.fatal,
104  result.continuable)
105  return result.return_
106 
107 
109  binary = re.compile('[\x00-\x08\x0B\x0C\x0E-\x1F]')
110  non_ascii = re.compile('[\x80-\xff]')
111 
112  def coerce(self, argument):
113  for handles, handler in [(is_string, self._handle_string_handle_string),
114  (is_bytes, self._handle_bytes_handle_bytes),
115  (is_number, self._pass_through_pass_through),
116  (is_dict_like, self._coerce_dict_coerce_dict),
117  (is_list_like, self._coerce_list_coerce_list),
118  (lambda arg: True, self._to_string_to_string)]:
119  if handles(argument):
120  return handler(argument)
121 
122  def _handle_string(self, arg):
123  if self._string_contains_binary_string_contains_binary(arg):
124  return self._handle_binary_in_string_handle_binary_in_string(arg)
125  return arg
126 
127  def _string_contains_binary(self, arg):
128  return (self.binarybinary.search(arg) or
129  is_bytes(arg) and self.non_asciinon_ascii.search(arg))
130 
131  def _handle_binary_in_string(self, arg):
132  try:
133  if not is_bytes(arg):
134  # Map Unicode code points to bytes directly
135  arg = arg.encode('latin-1')
136  except UnicodeError:
137  raise ValueError('Cannot represent %r as binary.' % arg)
138  return xmlrpc.client.Binary(arg)
139 
140  def _handle_bytes(self, arg):
141  return xmlrpc.client.Binary(arg)
142 
143  def _pass_through(self, arg):
144  return arg
145 
146  def _coerce_list(self, arg):
147  return [self.coercecoerce(item) for item in arg]
148 
149  def _coerce_dict(self, arg):
150  return dict((self._to_key_to_key(key), self.coercecoerce(arg[key])) for key in arg)
151 
152  def _to_key(self, item):
153  item = self._to_string_to_string(item)
154  self._validate_key_validate_key(item)
155  return item
156 
157  def _to_string(self, item):
158  item = safe_str(item) if item is not None else ''
159  return self._handle_string_handle_string(item)
160 
161  def _validate_key(self, key):
162  if isinstance(key, xmlrpc.client.Binary):
163  raise ValueError('Dictionary keys cannot be binary. Got %r.' % (key.data,))
164 
165 
167 
168  def __init__(self, result):
169  if not (is_dict_like(result) and 'status' in result):
170  raise RuntimeError('Invalid remote result dictionary: %s' % result)
171  self.statusstatus = result['status']
172  self.outputoutput = safe_str(self._get_get(result, 'output'))
173  self.return_return_ = self._get_get(result, 'return')
174  self.errorerror = safe_str(self._get_get(result, 'error'))
175  self.tracebacktraceback = safe_str(self._get_get(result, 'traceback'))
176  self.fatalfatal = bool(self._get_get(result, 'fatal', False))
177  self.continuablecontinuable = bool(self._get_get(result, 'continuable', False))
178 
179  def _get(self, result, key, default=''):
180  value = result.get(key, default)
181  return self._convert_convert(value)
182 
183  def _convert(self, value):
184  if isinstance(value, xmlrpc.client.Binary):
185  return bytes(value.data)
186  if is_dict_like(value):
187  return DotDict((k, self._convert_convert(v)) for k, v in value.items())
188  if is_list_like(value):
189  return [self._convert_convert(v) for v in value]
190  return value
191 
192 
194 
195  def __init__(self, uri, timeout=None):
196  self.uriuri = uri
197  self.timeouttimeout = timeout
198 
199  @property
200  @contextmanager
201  _server = property
202 
203  def _server(self):
204  if self.uriuri.startswith('https://'):
205  transport = TimeoutHTTPSTransport(timeout=self.timeouttimeout)
206  else:
207  transport = TimeoutHTTPTransport(timeout=self.timeouttimeout)
208  server = xmlrpc.client.ServerProxy(self.uriuri, encoding='UTF-8',
209  transport=transport)
210  try:
211  yield server
212  except (socket.error, xmlrpc.client.Error) as err:
213  raise TypeError(err)
214  finally:
215  server('close')()
216 
218  with self._server_server_server as server:
219  return server.get_library_information()
220 
221  def get_keyword_names(self):
222  with self._server_server_server as server:
223  return server.get_keyword_names()
224 
225  def get_keyword_arguments(self, name):
226  with self._server_server_server as server:
227  return server.get_keyword_arguments(name)
228 
229  def get_keyword_types(self, name):
230  with self._server_server_server as server:
231  return server.get_keyword_types(name)
232 
233  def get_keyword_tags(self, name):
234  with self._server_server_server as server:
235  return server.get_keyword_tags(name)
236 
237  def get_keyword_documentation(self, name):
238  with self._server_server_server as server:
239  return server.get_keyword_documentation(name)
240 
241  def run_keyword(self, name, args, kwargs):
242  with self._server_server_server as server:
243  run_keyword_args = [name, args, kwargs] if kwargs else [name, args]
244  try:
245  return server.run_keyword(*run_keyword_args)
246  except xmlrpc.client.Fault as err:
247  message = err.faultString
248  except socket.error as err:
249  message = 'Connection to remote server broken: %s' % err
250  except ExpatError as err:
251  message = ('Processing XML-RPC return value failed. '
252  'Most often this happens when the return value '
253  'contains characters that are not valid in XML. '
254  'Original error was: ExpatError: %s' % err)
255  raise RuntimeError(message)
256 
257 
258 # Custom XML-RPC timeouts based on
259 # http://stackoverflow.com/questions/2425799/timeout-for-xmlrpclib-client-requests
260 
261 class TimeoutHTTPTransport(xmlrpc.client.Transport):
262 
265  _connection_class = http.client.HTTPConnection
266 
267  def __init__(self, use_datetime=0, timeout=None):
268  xmlrpc.client.Transport.__init__(self, use_datetime)
269  if not timeout:
270  timeout = socket._GLOBAL_DEFAULT_TIMEOUT
271  self.timeouttimeout = timeout
272 
273  def make_connection(self, host):
274  if self._connection_connection and host == self._connection_connection[0]:
275  return self._connection_connection[1]
276  chost, self._extra_headers, x509 = self.get_host_info(host)
277  self._connection_connection = host, self._connection_class_connection_class(chost, timeout=self.timeouttimeout)
278  return self._connection_connection[1]
279 
280 
282 
285  _connection_class = http.client.HTTPSConnection
Used by Remote library to report remote errors.
Definition: errors.py:332
def _handle_binary_in_string(self, arg)
Definition: Remote.py:131
def _string_contains_binary(self, arg)
Definition: Remote.py:127
def __init__(self, result)
Definition: Remote.py:168
def _get(self, result, key, default='')
Definition: Remote.py:179
def __init__(self, uri='http://127.0.0.1:8270', timeout=None)
Connects to a remote server at uri.
Definition: Remote.py:45
def get_keyword_tags(self, name)
Definition: Remote.py:90
def get_keyword_types(self, name)
Definition: Remote.py:86
def _is_lib_info_available(self)
Definition: Remote.py:65
def _get_kw_info(self, kw, info, getter, default=None)
Definition: Remote.py:78
def get_keyword_documentation(self, name)
Definition: Remote.py:93
def get_keyword_arguments(self, name)
Definition: Remote.py:74
def run_keyword(self, name, args, kwargs)
Definition: Remote.py:96
def __init__(self, use_datetime=0, timeout=None)
Definition: Remote.py:267
def run_keyword(self, name, args, kwargs)
Definition: Remote.py:241
def __init__(self, uri, timeout=None)
Definition: Remote.py:195
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_dict_like(item)
Definition: robottypes.py:72
def is_list_like(item)
Definition: robottypes.py:66
def safe_str(item)
Definition: unic.py:21