def expect(self, **kwargs): for name, routes in iteritems(kwargs): service = self._registry.setdefault(name, ServiceMock({})) service.routes.update(routes) setattr(self._config, name, 'http://' + name + '/') return self
def group(self, futures, callback=None, name=None): if callable(callback): results_holder = {} group_callback = self.handler.finish_group.add( partial(callback, results_holder)) def delay_cb(): IOLoop.instance().add_callback( self.handler.check_finished(group_callback)) async_group = AsyncGroup(delay_cb, logger=self.handler.log, name=name) def future_callback(name, future): results_holder[name] = future.result() for name, future in iteritems(futures): if future.done(): future_callback(name, future) else: self.handler.add_future( future, async_group.add(partial(future_callback, name))) async_group.try_finish() return futures
def request_to_xml(request): content_type = request.headers.get('Content-Type', '') body = etree.Element('body', content_type=content_type) if request.body: try: if 'json' in content_type: body.text = _pretty_print_json(json.loads(request.body)) elif 'protobuf' in content_type: body.text = repr(request.body) else: body_query = urlparse.parse_qs(str(request.body), True) for name, values in iteritems(body_query): for value in values: body.append( E.param(to_unicode(value), name=to_unicode(name))) except Exception: debug_log.exception('cannot parse request body') body.text = repr(request.body) try: request = E.request(body, E.start_time(_format_number(request.start_time)), E.method(request.method), E.url(request.url), _params_to_xml(request.url), _headers_to_xml(request.headers), _cookies_to_xml(request.headers), E.curl(request_to_curl_string(request))) except Exception: debug_log.exception('cannot parse request body') body.text = repr(request.body) request = E.request(body) return request
def _parse_response(self, future, callback, error_callback, parse_response, parse_on_error, response): data = None result = RequestResult() try: if response.error and not parse_on_error: self._set_response_error(response) elif not parse_response: data = response.body elif response.code != 204: content_type = response.headers.get('Content-Type', '') for k, v in iteritems(DEFAULT_REQUEST_TYPES): if k.search(content_type): data = v(response, logger=self.handler.log) break except FailedRequestException as ex: result.set_exception(ex) if callable(error_callback) and (response.error or result.exception is not None): error_callback(data, response) elif callable(callback): callback(data, response) result.set(data, response) future.set_result(result)
def _headers_to_xml(request_or_response_headers): headers = etree.Element('headers') for name, value in iteritems(request_or_response_headers): if name != 'Cookie': str_value = value if isinstance(value, basestring_type) else str(value) headers.append(E.header(to_unicode(str_value), name=name)) return headers
def update_user_info(self, user_id=None, ip=None, username=None, email=None): new_data = { 'id': user_id, 'username': username, 'email': email, 'ip_address': ip, } new_data = {k: v for k, v in iteritems(new_data) if v is not None} self.user_info.update(new_data)
def make_qs(query_args): kv_pairs = [] for key, val in iteritems(query_args): if val is not None: encoded_key = _encode(key) if isinstance(val, (set, frozenset, list, tuple)): for v in val: kv_pairs.append((encoded_key, _encode(v))) else: kv_pairs.append((encoded_key, _encode(val))) return urlencode(kv_pairs)
def _params_to_xml(url, logger=debug_log): params = etree.Element('params') query = frontik.util.get_query_parameters(url) for name, values in iteritems(query): for value in values: try: params.append(E.param(to_unicode(value), name=to_unicode(name))) except UnicodeDecodeError: logger.exception('cannot decode parameter name or value') params.append(E.param(repr(value), name=repr(name))) return params
def write_error(self, status_code=500, **kwargs): # write_error in Frontik must be asynchronous when handling custom errors (due to XSLT) # e.g. raise HTTPError(503) is syncronous and generates a standard Tornado error page, # whereas raise HTTPError(503, xml=...) will call finish_with_postprocessors() # the solution is to move self.finish() from send_error to write_error # so any write_error override must call either finish() or finish_with_postprocessors() in the end # in Tornado 3 it may be better to rewrite this mechanism with futures if 'exc_info' in kwargs: exception = kwargs['exc_info'][1] else: exception = None headers = getattr(exception, 'headers', None) override_content = any( getattr(exception, x, None) is not None for x in ('text', 'xml', 'json')) finish_with_exception = exception is not None and ( 199 < status_code < 400 or # raise HTTPError(200) to finish page immediately override_content) if headers: for (name, value) in iteritems(headers): self.set_header(name, value) if finish_with_exception: self.json.clear() if getattr(exception, 'text', None) is not None: self.doc.clear() self.text = exception.text elif getattr(exception, 'json', None) is not None: self.text = None self.doc.clear() self.json.put(exception.json) elif getattr(exception, 'xml', None) is not None: self.text = None # cannot clear self.doc due to backwards compatibility, a bug actually self.doc.put(exception.xml) self.finish_with_postprocessors() return self.set_header('Content-Type', 'text/html; charset=UTF-8') return super(BaseHandler, self).write_error(status_code, **kwargs)
def __init__(self, **application_kwargs): self.log = getLogger('service_mock') self._config = EmptyEnvironment.LocalHandlerConfig() self._request = tornado.httpserver.HTTPRequest('GET', '/', remote_ip='127.0.0.1') self._request.connection = DummyConnection() self._registry = {} self._response_text = None self.application_kwargs = application_kwargs for (key, value) in iteritems(application_kwargs): setattr(self, key, value)
def write_error(self, status_code=500, **kwargs): # write_error in Frontik must be asynchronous when handling custom errors (due to XSLT) # e.g. raise HTTPError(503) is syncronous and generates a standard Tornado error page, # whereas raise HTTPError(503, xml=...) will call finish_with_postprocessors() # the solution is to move self.finish() from send_error to write_error # so any write_error override must call either finish() or finish_with_postprocessors() in the end # in Tornado 3 it may be better to rewrite this mechanism with futures if 'exc_info' in kwargs: exception = kwargs['exc_info'][1] else: exception = None headers = getattr(exception, 'headers', None) override_content = any(getattr(exception, x, None) is not None for x in ('text', 'xml', 'json')) finish_with_exception = exception is not None and ( 199 < status_code < 400 or # raise HTTPError(200) to finish page immediately override_content ) if headers: for (name, value) in iteritems(headers): self.set_header(name, value) if finish_with_exception: self.json.clear() if getattr(exception, 'text', None) is not None: self.doc.clear() self.text = exception.text elif getattr(exception, 'json', None) is not None: self.text = None self.doc.clear() self.json.put(exception.json) elif getattr(exception, 'xml', None) is not None: self.text = None # cannot clear self.doc due to backwards compatibility, a bug actually self.doc.put(exception.xml) self.finish_with_postprocessors() return self.set_header('Content-Type', 'text/html; charset=UTF-8') return super(BaseHandler, self).write_error(status_code, **kwargs)
def _call_function(self, handler_class, raise_exceptions=True): # Create application with the only route — handler_class application = application_mock([('', handler_class)], self._config)(**{ 'app': 'frontik.testing', }) for (key, value) in iteritems(self.application_kwargs): setattr(application, key, value) # Mock methods def fetch(request, callback, **kwargs): IOLoop.instance().add_callback( partial(self._fetch_mock, request, callback, **kwargs)) application.curl_http_client.fetch = fetch # raise_exceptions kwarg is deprecated if raise_exceptions: exceptions = [] old_handle_request_exception = handler_class._handle_request_exception def handle_request_exception(handler, e): old_handle_request_exception(handler, e) exceptions.append(sys.exc_info()) handler_class._handle_request_exception = handle_request_exception old_flush = handler_class.flush def flush(handler, *args, **kwargs): self._response_text = b''.join(handler._write_buffer) old_flush(handler, *args, **kwargs) IOLoop.instance().add_callback(IOLoop.instance().stop) handler_class.flush = flush self._handler = application(self._request) IOLoop.instance().start() if raise_exceptions and exceptions: raise_exc_info(exceptions[0]) return TestResult(self._config, self._request, self._handler, self._response_text)
def handle_return_value(self, handler_method_name, return_value): def _fail_on_error_wrapper(name, data, response): error_method_name = handler_method_name + '_requests_failed' if hasattr(self, error_method_name): getattr(self, error_method_name)(name, data, response) status_code = response.code if 300 <= response.code < 500 else 502 raise HTTPError(status_code, 'HTTP request failed with code {}'.format(response.code)) def _httpfuture_fail_on_error_result_check(name, future): result = future.result() if not result.response.error and not result.exception: return error_method_name = handler_method_name + '_requests_failed' if hasattr(self, error_method_name): getattr(self, error_method_name)(name, result.data, result.response) status_code = result.response.code if 300 <= result.response.code < 500 else 502 raise HTTPError(status_code, 'HTTP request failed with code {}'.format(result.response.code)) if isinstance(return_value, dict): futures = {} for name, req in iteritems(return_value): if isinstance(req, Future): if isinstance(req, HTTPResponseFuture) and req.fail_on_error: self.add_future(req, partial(_httpfuture_fail_on_error_result_check, name)) futures[name] = req else: req_type = getattr(req, 'method', None) if req_type not in self._METHODS_MAPPING: raise Exception('Invalid request object: {!r}'.format(req)) if req.kwargs.pop('fail_on_error'): req.kwargs['error_callback'] = partial(_fail_on_error_wrapper, name) method = self._METHODS_MAPPING[req_type] url = self.make_url(req.host, req.uri) futures[name] = method(url, **req.kwargs) done_method_name = handler_method_name + '_requests_done' self._http_client.group(futures, getattr(self, done_method_name, None), name='MicroHandler') elif return_value is not None: raise Exception('Invalid return type: {}'.format(type(return_value)))
def handle_return_value(self, handler_method_name, return_value): def _future_fail_on_error_handler(name, future): result = future.result() if not isinstance(result, RequestResult): return if not result.response.error and not result.exception: return error_method_name = handler_method_name + '_requests_failed' if hasattr(self, error_method_name): getattr(self, error_method_name)(name, result.data, result.response) status_code = result.response.code if 300 <= result.response.code < 500 else 502 raise HTTPError( status_code, 'HTTP request failed with code {}'.format( result.response.code)) if isinstance(return_value, dict): futures = {} for name, future in iteritems(return_value): # Use is_future with Tornado 4 if not isinstance(future, Future): raise Exception( 'Invalid MicroHandler return value: {!r}'.format( future)) if getattr(future, 'fail_on_error', False): self.add_future( future, self.finish_group.add( partial(_future_fail_on_error_handler, name))) futures[name] = future done_method_name = handler_method_name + '_requests_done' self._http_client.group(futures, getattr(self, done_method_name, None), name='MicroHandler') elif return_value is not None: raise Exception('Invalid return type: {}'.format( type(return_value)))
def group(self, futures, callback=None, name=None): if callable(callback): results_holder = {} group_callback = self.handler.finish_group.add( self.handler.check_finished(callback, results_holder)) async_group = AsyncGroup(group_callback, name=name) def future_callback(name, future): results_holder[name] = future.result() for name, future in iteritems(futures): if future.done(): future_callback(name, future) else: self.handler.add_future( future, async_group.add(partial(future_callback, name))) async_group.try_finish_async() return futures
def group(self, futures, callback=None, name=None): if callable(callback): results_holder = {} group_callback = self.handler.finish_group.add(partial(callback, results_holder)) def delay_cb(): IOLoop.instance().add_callback(self.handler.check_finished(group_callback)) async_group = AsyncGroup(delay_cb, logger=self.handler.log, name=name) def future_callback(name, future): results_holder[name] = future.result() for name, future in iteritems(futures): if future.done(): future_callback(name, future) else: self.handler.add_future(future, async_group.add(partial(future_callback, name))) async_group.try_finish() return futures
def write_error(self, status_code=500, **kwargs): """`write_error` can call `finish` asynchronously. This allows, for example, asynchronous templating on error pages. """ if 'exc_info' in kwargs: exception = kwargs['exc_info'][1] else: exception = None headers = getattr(exception, 'headers', None) override_content = any(getattr(exception, x, None) is not None for x in ('text', 'xml', 'json')) finish_with_exception = isinstance(exception, HTTPError) and override_content if headers: for (name, value) in iteritems(headers): self.set_header(name, value) if finish_with_exception: self.json.clear() if getattr(exception, 'text', None) is not None: self.doc.clear() self.text = exception.text elif getattr(exception, 'json', None) is not None: self.text = None self.doc.clear() self.json.put(exception.json) elif getattr(exception, 'xml', None) is not None: self.text = None # cannot clear self.doc due to backwards compatibility, a bug actually self.doc.put(exception.xml) self.finish_with_postprocessors() return self.set_header('Content-Type', 'text/html; charset=UTF-8') return super(BaseHandler, self).write_error(status_code, **kwargs)
def add_arguments(self, arguments): for key, val in iteritems(arguments): self._request.arguments[key] = [val] if isinstance( val, basestring_type) else val return self
def get_error_node(exception): return { 'error': {k: v for k, v in iteritems(exception.attrs)} }
def response_to_xml(response): time_info = etree.Element('time_info') content_type = response.headers.get('Content-Type', '') mode = '' if 'charset' in content_type: charset = content_type.partition('=')[-1] else: charset = 'utf-8' try_charsets = (charset, 'cp1251') try: if 'text/html' in content_type: body = frontik.util.decode_string_from_charset( response.body, try_charsets) body = body.replace('\n', '\\n').replace("'", "\\'").replace("<", "<") elif 'protobuf' in content_type: body = repr(response.body) elif response.body is None: body = '' elif 'xml' in content_type: mode = 'xml' body = _pretty_print_xml(etree.fromstring(response.body)) elif 'json' in content_type: mode = 'javascript' body = _pretty_print_json(json.loads(response.body)) else: if 'javascript' in content_type: mode = 'javascript' body = frontik.util.decode_string_from_charset( response.body, try_charsets) except Exception: debug_log.exception('cannot parse response body') body = repr(response.body) try: for name, value in iteritems(response.time_info): time_info.append(E.time(str(value), name=name)) except Exception: debug_log.exception('cannot append time info') try: response = E.response( E.body(body, content_type=content_type, mode=mode), E.code(str(response.code)), E.effective_url(response.effective_url), E.error(str(response.error)), E.size( str(len(response.body)) if response.body is not None else '0'), E.request_time(_format_number(response.request_time * 1000)), _headers_to_xml(response.headers), time_info, ) except Exception: debug_log.exception('cannot log response info') response = E.response(E.body('Cannot log response info')) return response
def make_mfd(fields, files): """ Constructs request body in multipart/form-data format fields :: { field_name : field_value } files :: { field_name: [{ "filename" : fn, "body" : bytes }]} """ def addslashes(text): for s in (b'\\', b'"'): if s in text: text = text.replace(s, b'\\' + s) return text def create_field(name, data): name = addslashes(any_to_bytes(name)) return [ b'--', BOUNDARY, b'\r\nContent-Disposition: form-data; name="', name, b'"\r\n\r\n', any_to_bytes(data), b'\r\n' ] def create_file_field(name, filename, data, content_type): if content_type == 'application/unknown': content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream' else: content_type = content_type.replace('\n', ' ').replace('\r', ' ') name = addslashes(any_to_bytes(name)) filename = addslashes(any_to_bytes(filename)) return [ b'--', BOUNDARY, b'\r\nContent-Disposition: form-data; name="', name, b'"; filename="', filename, b'"\r\nContent-Type: ', any_to_bytes(content_type), b'\r\n\r\n', any_to_bytes(data), b'\r\n' ] body = [] for name, data in iteritems(fields): if data is None: continue if isinstance(data, list): for value in data: if value is not None: body.extend(create_field(name, value)) else: body.extend(create_field(name, data)) for name, files in iteritems(files): for file in files: body.extend(create_file_field( name, file['filename'], file['body'], file.get('content_type', 'application/unknown') )) body.extend([b'--', BOUNDARY, b'--\r\n']) content_type = b'multipart/form-data; boundary=' + BOUNDARY return b''.join(body), content_type
def get_error_node(exception): return etree.Element('error', **{k: str(v) for k, v in iteritems(exception.attrs)})
def _encode_dict(d): return {k: _encode_value(v) for k, v in iteritems(d)}
def extend_request_arguments(request, match): arguments = match.groupdict() for name, value in iteritems(arguments): if value: request.arguments.setdefault(name, []).append(value)
def get_error_node(exception): return etree.Element( 'error', **{k: str(v) for k, v in iteritems(exception.attrs)})
def configure_app(self, **kwargs): """Updates or adds options to application config.""" for name, val in iteritems(kwargs): setattr(self._app.config, name, val) return self
def get_error_node(exception): return {'error': {k: v for k, v in iteritems(exception.attrs)}}
def configure(self, **kwargs): for name, val in iteritems(kwargs): setattr(self._config, name, val) return self