# -*- coding: utf-8 -*- # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Kite completions HTTP client.""" # Standard library imports import logging from urllib.parse import quote # Third party imports from qtpy.QtCore import QObject, QThread, Signal, QMutex import requests # Local imports from spyder.config.base import _, running_under_pytest from spyder.plugins.completion.providers.kite import ( KITE_ENDPOINTS, KITE_REQUEST_MAPPING) from spyder.plugins.completion.providers.kite.decorators import class_register from spyder.plugins.completion.providers.kite.providers import ( KiteMethodProviderMixIn) from spyder.plugins.completion.providers.kite.utils.status import status from spyder.py3compat import ( ConnectionError, ConnectionRefusedError, TEXT_TYPES) logger = logging.getLogger(__name__) @class_register class KiteClient(QObject, KiteMethodProviderMixIn): sig_response_ready = Signal(int, dict) sig_client_started = Signal(list) sig_client_not_responding = Signal() sig_perform_request = Signal(int, str, object) sig_perform_status_request = Signal(str) sig_status_response_ready = Signal((str,), (dict,)) sig_perform_onboarding_request = Signal() sig_onboarding_response_ready = Signal(str) sig_client_wrong_response = Signal(str, object) def __init__(self, parent, enable_code_snippets=True): QObject.__init__(self, parent) self.endpoint = None self.requests = {} self.languages = [] self.mutex = QMutex() self.opened_files = {} self.opened_files_status = {} self.thread_started = False self.enable_code_snippets = enable_code_snippets self.thread = QThread() self.moveToThread(self.thread) self.thread.started.connect(self.started) self.sig_perform_request.connect(self.perform_request) self.sig_perform_status_request.connect(self.get_status) self.sig_perform_onboarding_request.connect(self.get_onboarding_file) def start(self): if not self.thread_started: self.thread.start() logger.debug('Starting Kite HTTP session...') self.endpoint = requests.Session() self.languages = self.get_languages() self.sig_client_started.emit(self.languages) def started(self): self.thread_started = True def stop(self): if self.thread_started: logger.debug('Closing Kite HTTP session...') self.endpoint.close() self.thread.quit() def get_languages(self): verb, url = KITE_ENDPOINTS.LANGUAGES_ENDPOINT success, response = self.perform_http_request(verb, url) if not success: return [] if response is None or isinstance(response, TEXT_TYPES): response = ['python'] return response def _get_onboarding_file(self): """Perform a request to get kite's onboarding file.""" verb, url = KITE_ENDPOINTS.ONBOARDING_ENDPOINT success, response = self.perform_http_request(verb, url) return response def get_onboarding_file(self): """Get onboarding file.""" onboarding_file = self._get_onboarding_file() self.sig_onboarding_response_ready.emit(onboarding_file) def _get_status(self, filename): """Perform a request to get kite status for a file.""" verb, url = KITE_ENDPOINTS.STATUS_ENDPOINT if filename: url_params = {'filename': filename} else: url_params = {'filetype': 'python'} success, response = self.perform_http_request( verb, url, url_params=url_params) return success, response def get_status(self, filename): """Get kite status for a given filename.""" success_status, kite_status = self._get_status(filename) if not filename or kite_status is None: kite_status = status() self.sig_status_response_ready[str].emit(kite_status) elif isinstance(kite_status, TEXT_TYPES): status_str = status(extra_status=' with errors') long_str = _("{error}

" "Note: If you are using a VPN, " "please don't route requests to " "localhost/127.0.0.1 with it").format( error=kite_status) kite_status_dict = { 'status': status_str, 'short': status_str, 'long': long_str} self.sig_status_response_ready[dict].emit(kite_status_dict) else: self.sig_status_response_ready[dict].emit(kite_status) def perform_http_request(self, verb, url, url_params=None, params=None): response = None http_method = getattr(self.endpoint, verb) try: http_response = http_method(url, params=url_params, json=params) except Exception: return False, None success = http_response.status_code == 200 if success: try: response = http_response.json() except Exception: response = http_response.text response = None if response == '' else response return success, response def send(self, method, params, url_params): response = None if self.endpoint is not None and method in KITE_REQUEST_MAPPING: http_verb, path = KITE_REQUEST_MAPPING[method] encoded_url_params = { key: quote(value) if isinstance(value, TEXT_TYPES) else value for (key, value) in url_params.items()} path = path.format(**encoded_url_params) try: success, response = self.perform_http_request( http_verb, path, params=params) except (ConnectionRefusedError, ConnectionError): return response return response def perform_request(self, req_id, method, params): response = None if method in self.sender_registry: logger.debug('Perform request {0} with id {1}'.format( method, req_id)) handler_name = self.sender_registry[method] handler = getattr(self, handler_name) response = handler(params) if method in self.handler_registry: converter_name = self.handler_registry[method] converter = getattr(self, converter_name) if response is not None: response = converter(response) if not isinstance(response, (dict, type(None))): if not running_under_pytest(): self.sig_client_wrong_response.emit(method, response) else: self.sig_response_ready.emit(req_id, response or {})