示例#1
0
async def request_locker(servers: t.Union[Server, t.List[Server]],
                         action,
                         scope,
                         applicant,
                         auth=None,
                         datemark=None) -> t.List[Response]:
    tasks = []
    server_responses = []
    if is_iterable_not_string(servers):
        it = servers
    else:
        it = [servers]
    payload = dict(scope=scope.name, applicant=applicant)
    if datemark:
        payload.update(datemark=datemark)

    for server in it:
        tasks.append(
            create_task(
                async_post(server,
                           'api_1_0.locker_' + action,
                           json=payload,
                           auth=auth,
                           timeout=defaults.TIMEOUT_LOCK_REQUEST)))
    for server in it:
        r = await tasks.pop(0)
        server_responses.append(r)

    return server_responses
示例#2
0
def lock_scope(scope: Scope,
               servers: t.Union[t.List[Server], Server] = None,
               bypass: t.Union[t.List[Server], Server] = None,
               retries=0,
               delay=3,
               applicant=None,
               identity=None):

    if not locker_scope_enabled(scope):
        yield None
        return

    if servers is not None:
        servers = servers if is_iterable_not_string(servers) else [servers]
        if len(servers) == 0:
            servers = None

    servers = servers or get_servers_from_scope(scope, bypass)

    logger.debug(
        f"Requesting Lock on {scope.name} to the following servers: {[s.name for s in servers]}"
    )
    _try = 1

    applicant = lock(scope,
                     servers=servers,
                     applicant=applicant,
                     retries=retries,
                     delay=delay,
                     identity=identity)
    try:
        yield applicant
    finally:
        unlock(scope, servers=servers, applicant=applicant, identity=identity)
示例#3
0
    def _execute(self, params: Kwargs, timeout=None, context: Context = None):
        input_params = params['input']
        cp = CompletedProcess()
        cp.set_start_time()
        try:
            with lock_scope(Scope.CATALOG,
                            retries=3,
                            delay=4,
                            applicant=context.env.get('orch_execution_id')):
                server_names = input_params.get('server_names', [])
                if not is_iterable_not_string(server_names):
                    server_names = [server_names]
                to_be_deleted = Server.query.filter(
                    Server.name.in_(server_names)).all()

                for s in to_be_deleted:
                    # remove associated routes
                    db.session.delete(s.route)
                    s.delete()
                    db.session.commit()

        except errors.LockError as e:
            cp.success = False
            cp.stderr = str(e)
        except Exception as e:
            cp.success = False
            cp.stderr = f"Unable to delete server{'s' if len(input_params.get('to_be_deleted', [])) > 1 else ''} " \
                        f"{', '.join([s.name for s in to_be_deleted])}. Exception: {e}"
        else:
            cp.success = True
            cp.stdout = f"Server{'s' if len(to_be_deleted) > 1 else ''} " \
                        f"{', '.join([s._old_name for s in to_be_deleted])} deleted"

        cp.set_end_time()
        return cp
示例#4
0
 def patch(self, orchestration_id):
     o = Orchestration.query.get_or_raise(orchestration_id)
     for k, v in request.get_json().items():
         if k == 'description':
             v = '\n'.join(v) if is_iterable_not_string(v) else v
         setattr(o, k, v)
     db.session.commit()
     return {}, 204
示例#5
0
 def __init__(self, entity: str, ident, columns: t.Iterable[str] = None):
     self.entity = entity
     self.ident = ident
     if is_iterable_not_string(columns):
         self.columns = columns
     elif is_string_types(columns):
         self.columns = [columns]
     else:
         self.columns = ['id']
示例#6
0
    def _execute(self, params: Kwargs, timeout=None, context: Context = None):
        input_params = params['input']
        start = time.time()
        running = []
        cp = CompletedProcess()
        cp.set_start_time()
        try:
            min_timeout = min(timeout, input_params.get('timeout', None))
        except TypeError:
            if timeout is not None:
                min_timeout = timeout
            elif input_params.get('timeout', None) is not None:
                min_timeout = input_params.get('timeout')
            else:
                min_timeout = defaults.MAX_TIME_WAITING_SERVERS

        server_names = input_params.get('server_names', [])
        if not is_iterable_not_string(server_names):
            server_names = [server_names]

        if not server_names:
            cp.success = False
            cp.stdout = f"No server to wait for DM running"
            cp.set_end_time()
            return cp
        pending_names = set(server_names)
        found_names = []
        while len(pending_names) > 0:
            try:
                found_names = db.session.query(Server.name).join(
                    Route, Route.destination_id == Server.id).filter(
                        Server.name.in_(pending_names)).filter(
                            Route.cost.isnot(None)).order_by(
                                Server.name).all()
            except sqlite3.OperationalError as e:
                if str(e) == 'database is locked':
                    pass
                else:
                    raise
            found_names = set([t[0] for t in found_names])
            pending_names = pending_names - found_names
            if pending_names and (time.time() - start) < min_timeout:
                time.sleep(self.system_kwargs.get('sleep_time', 15))
            else:
                break

        if not pending_names:
            cp.success = True
            cp.stdout = f"Server{'s' if len(server_names) > 1 else ''} " \
                        f"{', '.join(sorted(server_names))} with dimensigon running"
        else:
            cp.success = False
            cp.stderr = f"Server{'s' if len(pending_names) > 1 else ''} {', '.join(sorted(pending_names))} " \
                        f"with dimensigon not running after {min_timeout} seconds"
        cp.set_end_time()
        return cp
示例#7
0
    def get_completions(self, document: Document, complete_event: CompleteEvent, var_filters: dict = None) -> \
            Iterable[Completion]:
        url_filters = {}
        for filter in self.filters:
            source, dest = filter
            if var_filters:
                value = var_filters.get(dest, None)
                if value:
                    if is_iterable_not_string(value):
                        value = ','.join(value)
                    url_filters.update({f'filter[{dest}]': value})

        try:
            url = ntwrk.generate_url(self.resource, {
                **url_filters,
                **self.resource_params
            })
        except:
            return
        res = ntwrk.request('get', url, login=False, timeout=3)
        if res.code == 200:
            words = []
            meta_words = {}
            for e in res.msg:
                if isinstance(e, dict) and e.get(self.key) not in words:
                    words.append(e.get(self.key))
                    if self.meta_key or self.meta_format:
                        if self.meta_format:
                            if self.transforms:
                                for key, transform in self.transforms.items():
                                    if key in e:
                                        val = e.get(key)
                                        try:
                                            e['key'] = transform(val)
                                        except:
                                            pass
                            try:
                                format = self.meta_format.format(**e)
                            except:
                                pass
                            else:
                                meta_words[e.get(self.key)] = HTML(format)
                        else:
                            meta_words[e.get(self.key)] = HTML(
                                e.get(self.meta_key))
                elif isinstance(e, str):
                    words.append(e)

            completer = DshellWordCompleter(words,
                                            ignore_case=self.ignore_case,
                                            match_middle=self.match_middle,
                                            meta_dict=meta_words)
            for c in completer.get_completions(document, complete_event):
                yield c
示例#8
0
 def __init__(self,
              name: str,
              version: int,
              action_type: ActionType,
              code: MultiLine = None,
              expected_stdout: MultiLine = None,
              expected_stderr: MultiLine = None,
              expected_rc: int = None,
              system_kwargs: typos.Kwargs = None,
              pre_process: MultiLine = None,
              post_process: MultiLine = None,
              schema: typos.Kwargs = None,
              description: MultiLine = None,
              **kwargs):
     super().__init__(**kwargs)
     self.name = name
     self.version = version
     self.action_type = action_type
     self.code = '\n'.join(code) if is_iterable_not_string(code) else code
     self.schema = schema or {}
     self.expected_stdout = '\n'.join(
         expected_stdout) if is_iterable_not_string(
             expected_stdout) else expected_stdout
     self.expected_stderr = '\n'.join(
         expected_stderr) if is_iterable_not_string(
             expected_stderr) else expected_stderr
     self.expected_rc = expected_rc
     self.system_kwargs = system_kwargs or {}
     self.pre_process = '\n'.join(pre_process) if is_iterable_not_string(
         pre_process) else pre_process
     self.post_process = '\n'.join(post_process) if is_iterable_not_string(
         post_process) else post_process
     self.description = '\n'.join(description) if is_iterable_not_string(
         description) else description
示例#9
0
def _replace_path_args(path, args):
    replaced_path = path
    match = re.search(r'\<((\w+:)?([\w_]+))\>', path)
    while match:
        text = match.groups()[2]
        assert text in args
        value = args.pop(text)
        if not value:
            raise ValueError(f"No value specified for '{text}' in URL {path}")
        replaced_path = "{}{}{}".format(replaced_path[:match.start()], value,
                                        replaced_path[match.end():])
        match = re.search(r'\<((\w+:)?([\w_]+))\>', replaced_path)

    params = []
    if args:
        for k, v in args.items():
            if is_iterable_not_string(v):
                for vv in v:
                    if vv is not None:
                        params.append(f"{k}={urllib.parse.quote_plus(vv)}")
            else:
                if v is not None:
                    params.append(f"{k}={urllib.parse.quote_plus(v)}")
    return replaced_path + '?' + '&'.join(params)
示例#10
0
def launch_command():
    data = request.get_json()

    server_list = []
    if 'target' in data:
        not_found = []
        servers = Server.query.all()
        if data['target'] == 'all':
            server_list = servers
        elif is_iterable_not_string(data['target']):
            for vv in data['target']:
                sl = search(vv, servers)
                if len(sl) == 0:
                    not_found.append(vv)
                else:
                    server_list.extend(sl)
        else:
            sl = search(data['target'], servers)
            if len(sl) == 0:
                not_found.append(data['target'])
            else:
                server_list.extend(sl)
        if not_found:
            return {
                'error':
                "Following granules or ids did not match to any server: " +
                ', '.join(not_found)
            }, 404
    else:
        server_list.append(g.server)

    if re.search(r'rm\s+((-\w+|--[-=\w]*)\s+)*(-\w*[rR]\w*|--recursive)',
                 data['command']):
        return {'error': 'rm with recursion is not allowed'}, 403
    data.pop('target', None)
    start = None

    username = getattr(User.query.get(get_jwt_identity()), 'name', None)
    if not username:
        raise errors.EntityNotFound('User', get_jwt_identity())
    cmd = wrap_sudo(username, data['command'])
    if g.server in server_list:
        start = time.time()
        server_list.pop(server_list.index(g.server))
        proc = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE if data.get('input', None) else None,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            shell=True,
            close_fds=True,
            encoding='utf-8')

    resp_data = {}
    if check_param_in_uri("human"):
        attr = 'name'
    else:
        attr = 'id'
    if server_list:
        resp: t.List[ntwrk.Response] = asyncio.run(
            ntwrk.parallel_requests(server_list,
                                    method='POST',
                                    view_or_url='api_1_0.launch_command',
                                    json=data))
        for s, r in zip(server_list, resp):
            key = getattr(s, attr, s.id)
            if r.ok:
                resp_data[key] = r.msg[s.id]
            else:
                if not r.exception:
                    resp_data[key] = {
                        'error': {
                            'status_code': r.code,
                            'response': r.msg
                        }
                    }
                else:
                    if isinstance(r.exception, errors.BaseError):
                        resp_data[key] = errors.format_error_content(
                            r.exception, current_app.config['DEBUG'])
                    else:
                        resp_data[key] = {
                            'error':
                            format_exception(r.exception) if
                            current_app.config['DEBUG'] else str(r.exception)
                            or str(r.exception.__class__.__name__)
                        }

    if start:
        key = getattr(g.server, attr, g.server.id)
        timeout = data.get('timeout', defaults.TIMEOUT_COMMAND)
        try:
            outs, errs = proc.communicate(input=(data.get('input', '') or ''),
                                          timeout=timeout)
        except (TimeoutError, subprocess.TimeoutExpired):
            proc.kill()
            try:
                outs, errs = proc.communicate(timeout=1)
            except:
                resp_data[key] = {
                    'error':
                    f"Command '{cmd}' timed out after {timeout} seconds. Unable to communicate with the process launched."
                }
            else:
                resp_data[key] = {
                    'error':
                    f"Command '{cmd}' timed out after {timeout} seconds",
                    'stdout': outs.split('\n'),
                    'stderr': errs.split('\n')
                }
        except Exception as e:
            current_app.logger.exception(
                "Exception raised while trying to run command")
            resp_data[key] = {
                'error':
                traceback.format_exc() if current_app.config['DEBUG'] else
                str(r.exception) or str(r.exception.__class__.__name__)
            }
        else:
            resp_data[key] = {
                'stdout': outs.split('\n'),
                'stderr': errs.split('\n'),
                'returncode': proc.returncode
            }
    resp_data['cmd'] = cmd
    resp_data['input'] = data.get('input', None)
    return resp_data, 200
示例#11
0
def lock(scope: Scope,
         servers: t.List[Server] = None,
         applicant=None,
         retries=0,
         delay=3,
         identity=None) -> UUID:
    """
    locks the Locker if allowed
    Parameters
    ----------
    scope
        scope that lock will affect.
    servers
        if scope set to Scope.ORCHESTRATION,
    applicant
        identifier of the lock
    Returns
    -------
    Result
    """

    if servers is not None:
        servers = servers if is_iterable_not_string(servers) else [servers]
        if len(servers) == 0:
            servers = None

    servers = servers or get_servers_from_scope(scope)

    if len(servers) == 0:
        raise errors.NoServerToLock(scope)

    applicant = applicant if applicant is not None else [
        str(s.id) for s in servers
    ]

    _try = 1
    while True:
        try:
            lock_unlock(action='L',
                        scope=scope,
                        servers=servers,
                        applicant=applicant,
                        identity=identity)
        except errors.LockError as e:
            if _try < retries:
                _try += 1
                logger.info(
                    f"Retrying to lock on {scope.name} in {delay} seconds")
                time.sleep(delay)
            else:
                error_servers = [r.server for r in e.responses]
                locked_servers = list(
                    set(server for server in servers) - set(error_servers))
                lock_unlock('U',
                            scope,
                            servers=locked_servers,
                            applicant=applicant,
                            identity=identity)
                raise e
        else:
            break

    return applicant
示例#12
0
    async def _send_routes(self, exclude=None):

        servers = Server.get_neighbours(session=self.session)
        msg, debug_msg = self._format_routes_message(self._changed_routes)

        c_exclude = []
        if self.logger.level <= logging.DEBUG:
            if exclude:
                if is_iterable_not_string(exclude):
                    c_exclude = [
                        self.session.query(Server).get(e)
                        if not isinstance(e, Server) else e for e in exclude
                    ]
                else:
                    c_exclude = [
                        self.session.query(Server).get(exclude)
                        if not isinstance(exclude, Server) else exclude
                    ]
                log_msg = f" (Excluded nodes: {', '.join([getattr(e, 'name', e) for e in c_exclude])}):"
            else:
                log_msg = ''

            if servers:
                log_msg = f"Sending route information to the following nodes: {', '.join([s.name for s in servers])} " \
                          f"{log_msg}{json.dumps(debug_msg, indent=2)}"
            else:
                log_msg = f"No servers to send new routing information:{log_msg}{json.dumps(debug_msg, indent=2)}"
                if debug_msg:
                    log_msg += '\n' + json.dumps(debug_msg, indent=2)

            if debug_msg and (servers or exclude):
                self.logger.debug(log_msg)

        exclude_ids = list(
            set([s.id for s in servers
                 ]).union([getattr(e, 'id', e) for e in c_exclude]))

        auth = get_root_auth()
        aw = [
            ntwrk.async_patch(s,
                              view_or_url='api_1_0.routes',
                              json={
                                  'server_id': self.server.id,
                                  'route_list': msg,
                                  'exclude': exclude_ids
                              },
                              auth=auth) for s in servers
        ]

        rs = await asyncio.gather(*aw, return_exceptions=True)

        for r, s in zip(rs, servers):
            if isinstance(r, Exception):
                self.logger.warning(
                    f"Error while trying to send route data to node {s}: "
                    f"{format_exception(r)}")
            elif not r.ok:
                if r.exception:
                    self.logger.warning(
                        f"Error while trying to send route data to node {s}: "
                        f"{format_exception(r.exception)}")
                else:
                    self.logger.warning(
                        f"Error while trying to send route data to node {s}: {r}"
                    )
        self._changed_routes.clear()
示例#13
0
    def _execute(self, params: Kwargs, timeout=None, context: Context = None):
        input_params = params['input']
        start = time.time()
        found = []
        cp = CompletedProcess()
        cp.set_start_time()
        timeout = input_params.get('timeout')
        if timeout is None:
            timeout = self.system_kwargs.get('timeout')
        if timeout is None:
            timeout = defaults.MAX_TIME_WAITING_SERVERS

        try:
            now = get_now()
            server_names = input_params.get('server_names', [])
            if not is_iterable_not_string(server_names):
                server_names = [server_names]

            if not server_names:
                cp.success = False
                cp.stderr = f"No server to wait"
                cp.set_end_time()
                return cp

            pending_names = set(server_names)
            with lock_scope(Scope.CATALOG,
                            retries=3,
                            delay=4,
                            applicant=context.env.get('orch_execution_id')):
                while len(pending_names) > 0:
                    try:
                        found_names = db.session.query(Server.name).filter(
                            Server.name.in_(pending_names)).filter(
                                Server.created_on >= now).all()
                    except sqlite3.OperationalError as e:
                        if str(e) == 'database is locked':
                            found_names = []
                        else:
                            raise
                    found_names = set([t[0] for t in found_names
                                       ]) if found_names else set()
                    pending_names = pending_names - found_names
                    if pending_names and (time.time() - start) < timeout:
                        time.sleep(self.system_kwargs.get('sleep_time', 15))
                    else:
                        break
        except errors.LockError as e:
            cp.success = False
            cp.stderr = str(e)

        else:
            if not pending_names:
                cp.success = True
                cp.stdout = f"Server{'s' if len(server_names) > 1 else ''} " \
                            f"{', '.join(sorted(server_names))} found"
            else:
                cp.success = False
                cp.stderr = f"Server{'s' if len(pending_names) > 1 else ''} {', '.join(sorted(pending_names))} " \
                            f"not created after {timeout} seconds"
        cp.set_end_time()
        return cp
示例#14
0
    def get_completions(
            self, document: Document,
            complete_event: CompleteEvent) -> t.Iterable[Completion]:

        # TODO: Problem with completing positionals. Solve argument resolution to know in which positional.
        try:
            # Split document.
            text = document.text_before_cursor.lstrip()
            stripped_len = len(document.text_before_cursor) - len(text)

            if text.endswith('-h') or text.endswith('--help'):
                return
            # If there is a space, check for the first term, and use a
            # subcompleter.
            if " " in text:
                first_term = text.split()[0]
                completer = self.options.get(first_term)

                # If we have a sub completer, use this for the completions.
                if isinstance(completer, Completer):
                    remaining_text = text[len(first_term):].lstrip()
                    move_cursor = len(text) - len(
                        remaining_text) + stripped_len

                    new_document = Document(
                        remaining_text,
                        cursor_position=document.cursor_position - move_cursor,
                    )

                    for c in completer.get_completions(new_document,
                                                       complete_event):
                        yield c

                # we reached the bottom subcommand. Parse to see if we have to autocomplete an argument or its value
                else:
                    options = {}
                    params = {}
                    dest_args = {}

                    if not completer:
                        return
                    for arg in completer:
                        if isinstance(arg, dict):
                            arg = [arg]
                        elif isinstance(arg, list):
                            pass
                        else:
                            # to pass command function in dict command definition
                            continue
                        for a in arg:
                            if a.get('argument').startswith('-'):
                                options.update({a.get('argument'): a})
                            else:
                                params.update({a.get('argument'): a})

                            if 'dest' in a:
                                dest_args.update({a.get('dest'): a})
                            else:
                                dest_args.update(
                                    {a.get('argument').lstrip('-'): a})

                    try:
                        words = shlex.split(text)
                    except:
                        return
                    if len(words
                           ) > 0 and words[-1] in options and text.endswith(
                               words[-1]):
                        completer = DshellWordCompleter(
                            words=list(options.keys()))
                        for c in completer.get_completions(
                                document, complete_event):
                            yield c
                        for p in params:
                            if 'choices' in params[p]:
                                completer = DshellWordCompleter(
                                    params[p].get("choices"))
                            elif 'completer' in params[p]:
                                completer = params[p].get('completer')
                    else:
                        parser = create_parser(
                            completer, GuessArgumentParser(allow_abbrev=False))
                        finder = "F:I.N:D.E:R"
                        if document.char_before_cursor == ' ':
                            text = document.text + finder
                            current_word = finder
                        else:
                            text = document.text
                            current_word = document.get_word_before_cursor(
                                WORD=True)

                        namespace = parser.parse_args(shlex.split(text)[1:])
                        values = dict(namespace._get_kwargs())

                        # find key related to current_word
                        for k, v in values.items():
                            if is_iterable_not_string(v):
                                if current_word in v:
                                    break
                            else:
                                if v == current_word:
                                    break
                        else:
                            k = None
                            v = None

                        # special case for DictAction
                        for dest, arg_def in dest_args.items():
                            if 'action' in arg_def and arg_def[
                                    'action'] == DictAction and values[dest]:
                                for k, v in values[dest].items():
                                    # target
                                    if k == current_word:
                                        resp = ntwrk.get(
                                            'api_1_0.orchestrationresource',
                                            view_data={
                                                'orchestration_id':
                                                values['orchestration_id']
                                            })
                                        if resp.ok:
                                            needed_target = resp.msg['target']
                                            completer = DshellWordCompleter([
                                                target
                                                for target in needed_target
                                                if target not in
                                                values[dest].keys()
                                            ])
                                            for c in completer.get_completions(
                                                    document, complete_event):
                                                yield c
                                            return

                                    elif current_word in v:
                                        completer = arg_def.get(
                                            'completer', None)
                                        for c in completer.get_completions(
                                                document, complete_event):
                                            if c.text not in v:
                                                yield c
                                        if len(v) == 0 or len(
                                                v) == 1 and v[0] == finder:
                                            return
                                k = None
                                v = None

                        # get source value
                        if k:
                            nargs = dest_args[k].get('nargs')
                            if nargs and not isinstance(nargs, int):
                                if k not in params and document.char_before_cursor == ' ':
                                    if '--' not in words:
                                        # next argument may be a positional parameter or an optional argument
                                        # if nargs '+' means that at least 1 item must be provided
                                        if not (nargs == '+' and v and len(v) -
                                                1 == 0) and k not in params:
                                            completer = DshellWordCompleter(
                                                words=list(options.keys()))
                                            for c in completer.get_completions(
                                                    document, complete_event):
                                                yield c
                                        if nargs == '*' or (nargs == '+' and v
                                                            and
                                                            len(v) - 1 > 0):
                                            yield Completion('--')
                                    else:
                                        for p in params:
                                            if 'choices' in params[p]:
                                                completer = DshellWordCompleter(
                                                    params[p].get("choices"))
                                            elif 'completer' in params[p]:
                                                completer = params[p].get(
                                                    'completer')
                                            for c in completer.get_completions(
                                                    document, complete_event):
                                                yield c
                                            break
                                elif k in params and document.char_before_cursor == ' ':
                                    completer = DshellWordCompleter(
                                        words=list(options.keys()))
                                    for c in completer.get_completions(
                                            document, complete_event):
                                        yield c
                            # cursor is in params (not option) it may set an optional parameter
                            elif k in params and '--' not in words:
                                completer = DshellWordCompleter(
                                    words=list(options.keys()))
                                for c in completer.get_completions(
                                        document, complete_event):
                                    yield c
                            if k in dest_args:
                                if 'choices' in dest_args[k]:
                                    completer = DshellWordCompleter(
                                        dest_args[k].get("choices"))
                                    for c in completer.get_completions(
                                            document, complete_event):
                                        if (v and c.text
                                                not in v) or v is None:
                                            yield c
                                completer = dest_args[k].get('completer', None)
                            else:
                                completer = None
                            if completer:
                                if isinstance(completer, ResourceCompleter):
                                    kwargs = dict(var_filters=values)
                                else:
                                    kwargs = {}

                                for c in completer.get_completions(
                                        document, complete_event, **kwargs):
                                    if (v and c.text not in v) or v is None:
                                        yield c
                        else:
                            completer = DshellWordCompleter(
                                words=list(options.keys()))
                            for c in completer.get_completions(
                                    document, complete_event):
                                yield c
                            for p in params:
                                if getattr(namespace, p) is None:
                                    if 'choices' in params[p]:
                                        completer = DshellWordCompleter(
                                            params[p].get("choices"))
                                    elif 'completer' in params[p]:
                                        completer = params[p].get('completer')
                                    for c in completer.get_completions(
                                            document, complete_event):
                                        yield c

            # No space in the input: behave exactly like `WordCompleter`.
            else:
                completer = DshellWordCompleter(list(self.options.keys()),
                                                ignore_case=self.ignore_case)
                for c in completer.get_completions(document, complete_event):
                    yield c
        except Exception as e:
            dprint(e)