# Copyright (C) 2013-2016 DNAnexus, Inc. # # This file is part of dx-toolkit (DNAnexus platform client libraries). # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. ''' This file contains utility functions for interactive scripts such as dx for tab-completion, resolving naming conflicts, etc. ''' from __future__ import print_function, unicode_literals, division, absolute_import import sys from argcomplete import warn from collections import namedtuple, OrderedDict import dxpy from .resolver import (get_first_pos_of_char, get_last_pos_of_char, clean_folder_path, resolve_path, split_unescaped, ResolutionError) from .printing import fill def startswith(text): return (lambda string: string.startswith(text)) def escape_name(text): return escape_colon(text).replace('/', '\\/') def escape_colon(text): return text.replace(':', '\\:') def unescape_colon(text): return text.replace('\\:', ':') def join_path(project, path, name): project = escape_colon(project) path = escape_colon(path) name = escape_name(name) if not path.startswith('/'): path = '/' + path if not path.endswith('/'): path = path + '/' return project+":"+path+name def split_path(path): colon_pos = get_first_pos_of_char(':', path) slash_pos = get_last_pos_of_char('/', path) project, path, name = path[:colon_pos], path[colon_pos+1:slash_pos], path[slash_pos+1:] if path == '': path = '/' project = unescape_colon(project) path = unescape_colon(path) name = unescape_colon(name).replace('\\/', '/') return project, path, name # def completion_escaper(match): # if match.group(0) == ":": # return "\\\\:" # elif match.group(0) == "/": # return "\\\\/" # elif match.group(0) == "*": # return "\\\\\\\\*" # elif match.group(0) == "?": # return "\\\\\\\\\?" # else: # return "\\" + match.group(0) # def escape_completion_name_str(string): # return re.sub("([#\?\*: ;&`\"'/!$\(\)\{\[<>|~])", completion_escaper, string.replace('\\', '\\\\\\\\')) #def escape_completion_name_str(string): # return string.replace('\\', '\\\\\\\\').replace(' ', '\ ').replace(':', '\\\\:').replace('/', '\\\\/').replace('*', '\\\\\\\\*').replace('?', '\\\\\\\\?').replace('(', '\\(').replace(')', '\\)') # def unescape_completion_name_str(string): # return string.replace('\\)', ')').replace('\\(', '(').replace('\\\\\\\\?', '?').replace('\\\\\\\\*', '*').replace('\\\\/', '/').replace('\\\\:', ':').replace('\ ', ' ').replace('\\\\\\\\', '\\') def get_folder_matches(text, delim_pos, dxproj, folderpath): ''' :param text: String to be tab-completed; still in escaped form :type text: string :param delim_pos: index of last unescaped "/" in text :type delim_pos: int :param dxproj: DXProject handler to use :type dxproj: DXProject :param folderpath: Unescaped path in which to search for folder matches :type folderpath: string :returns: List of matches :rtype: list of strings Members of the returned list are guaranteed to start with *text* and be in escaped form for consumption by the command-line. ''' try: folders = dxproj.list_folder(folder=folderpath, only='folders')['folders'] folder_names = [name[name.rfind('/') + 1:] for name in folders] if text != '' and delim_pos != len(text) - 1: folder_names += ['.', '..'] prefix = text[:delim_pos + 1] return [prefix + f + '/' for f in folder_names if (prefix + f + '/').startswith(text)] except: return [] def get_data_matches(text, delim_pos, dxproj, folderpath, classname=None, typespec=None, visibility=None): ''' :param text: String to be tab-completed; still in escaped form :type text: string :param delim_pos: index of last unescaped "/" or ":" in text :type delim_pos: int :param dxproj: DXProject handler to use :type dxproj: DXProject :param folderpath: Unescaped path in which to search for data object matches :type folderpath: string :param classname: Data object class by which to restrict the search (None for no restriction on class) :type classname: string :param visibility: Visibility to constrain the results to; default is "visible" for empty strings, "either" for nonempty :type visibility: string :returns: List of matches :rtype: list of strings Members of the returned list are guaranteed to start with *text* and be in escaped form for consumption by the command-line. ''' #unescaped_text = unescape_completion_name_str(text[delim_pos + 1:]) unescaped_text = text[delim_pos + 1:] if visibility is None: if text != '' and delim_pos != len(text) - 1: visibility = "either" else: visibility = "visible" try: results = list(dxpy.find_data_objects(project=dxproj.get_id(), folder=folderpath, name=unescaped_text + "*", name_mode="glob", recurse=False, visibility=visibility, classname=classname, limit=100, describe=dict(fields=dict(name=True)), typename=typespec)) prefix = '' if text == '' else text[:delim_pos + 1] return [prefix + escape_name(result['describe']['name']) for result in results] except: return [] def path_completer(text, expected=None, classes=None, perm_level=None, include_current_proj=False, typespec=None, visibility=None): ''' :param text: String to tab-complete to a path matching the syntax project-name:folder/entity_or_folder_name :type text: string :param expected: "folder", "entity", "project", or None (no restriction) as to the types of answers to look for :type expected: string :param classes: if expected="entity", the possible data object classes that are acceptable :type classes: list of strings :param perm_level: the minimum permissions level required, e.g. "VIEW" or "CONTRIBUTE" :type perm_level: string :param include_current_proj: Indicate whether the current project's name should be a potential result :type include_current_proj: boolean :param visibility: Visibility with which to restrict the completion (one of "either", "visible", or "hidden") (default behavior is dependent on *text*) Returns a list of matches to the text and restricted by the requested parameters. ''' colon_pos = get_last_pos_of_char(':', text) slash_pos = get_last_pos_of_char('/', text) delim_pos = max(colon_pos, slash_pos) # First get projects if necessary matches = [] if expected == 'project' and colon_pos > 0 and colon_pos == len(text) - 1: if dxpy.find_one_project(zero_ok=True, name=text[:colon_pos]) is not None: return [text + " "] if colon_pos < 0 and slash_pos < 0: # Might be tab-completing a project, but don't ever include # whatever's set as dxpy.WORKSPACE_ID unless expected == "project" # Also, don't bother if text=="" and expected is NOT "project" # Also, add space if expected == "project" if text != "" or expected == 'project': results = dxpy.find_projects(describe=True, level=perm_level) if not include_current_proj: results = [r for r in results if r['id'] != dxpy.WORKSPACE_ID] matches += [escape_colon(r['describe']['name'])+':' for r in results if r['describe']['name'].startswith(text)] if expected == 'project': return matches # Attempt to tab-complete to a folder or data object name if colon_pos < 0 and slash_pos >= 0: # Not tab-completing a project, and the project is unambiguous # (use dxpy.WORKSPACE_ID) if dxpy.WORKSPACE_ID is not None: # try-catch block in case dxpy.WORKSPACE_ID is garbage try: dxproj = dxpy.get_handler(dxpy.WORKSPACE_ID) folderpath, entity_name = clean_folder_path(text) matches += get_folder_matches(text, slash_pos, dxproj, folderpath) if expected != 'folder': if classes is not None: for classname in classes: matches += get_data_matches(text, slash_pos, dxproj, folderpath, classname=classname, typespec=typespec, visibility=visibility) else: matches += get_data_matches(text, slash_pos, dxproj, folderpath, typespec=typespec, visibility=visibility) except: pass else: # project is given by a path, but attempt to resolve to an # object or folder anyway try: proj_ids, folderpath, entity_name = resolve_path(text, multi_projects=True) except ResolutionError as details: sys.stderr.write("\n" + fill(str(details))) return matches for proj in proj_ids: # protects against dxpy.WORKSPACE_ID being garbage try: dxproj = dxpy.get_handler(proj) matches += get_folder_matches(text, delim_pos, dxproj, folderpath) if expected != 'folder': if classes is not None: for classname in classes: matches += get_data_matches(text, delim_pos, dxproj, folderpath, classname=classname, typespec=typespec, visibility=visibility) else: matches += get_data_matches(text, delim_pos, dxproj, folderpath, typespec=typespec, visibility=visibility) except: pass return matches class DXPathCompleter(): ''' This class can be used as a tab-completer with the readline module Note that to tab-complete data object names with spaces, the delimiters set for the completer must not include spaces. ''' def __init__(self, expected=None, classes=None, typespec=None, include_current_proj=False, visibility=None): self.matches = [] self.expected = expected self.classes = classes self.typespec = typespec self.include_current_proj = include_current_proj self.visibility = visibility def _populate_matches(self, prefix): self.matches = path_completer(prefix, self.expected, self.classes, typespec=self.typespec, include_current_proj=self.include_current_proj, visibility=self.visibility) def get_matches(self, line, point, prefix, suffix): self._populate_matches(prefix) return self.matches def complete(self, text, state): if state == 0: self._populate_matches(text) if state < len(self.matches): return self.matches[state] else: return None class DXAppCompleter(): def __init__(self, installed=None): self.matches = [] self.installed = installed def _populate_matches(self, prefix): try: name_query = None if len(prefix) > 0: if prefix.startswith("app-") and len(prefix) > 4: name_query = prefix[4:] + "*" elif len(prefix) > 4 or not "app-".startswith(prefix): name_query = prefix + "*" appnames = [result['describe']['name'] for result in dxpy.find_apps(name=name_query, name_mode="glob", describe={"fields": {"name": True, "installed": (self.installed is not None)}}) if self.installed is None or (self.installed == result['describe']['installed'])] except: # This is for (temporary) backwards-compatibility appnames = [result['describe']['name'] for result in dxpy.find_apps(describe=True) if self.installed is None or (self.installed == result['describe']['installed'])] self.matches = [name for name in appnames if name.startswith(prefix)] if prefix != '' and prefix.startswith('app-'[:len(prefix)]): appnames_with_prefix = [('app-' + name) for name in appnames] self.matches += [name for name in appnames_with_prefix if name.startswith(prefix)] def get_matches(self, line, point, prefix, suffix): # This implementation is reliant on bash behavior that ':' is # treated as a word separator for determining prefix if get_last_pos_of_char(' ', line) != point - 1: prefix = split_unescaped(' ', line[:point])[-1] # Can immediately return if there are disallowed characters of # app names in the prefix if ':' in prefix: return [] self._populate_matches(prefix) return self.matches def complete(self, text, state): if state == 0: self._populate_matches(text) if state < len(self.matches): return self.matches[state] else: return None class LocalCompleter(): def __init__(self): self.matches = [] def _populate_matches(self, prefix): from argcomplete.completers import FilesCompleter completer = FilesCompleter() self.matches = completer(prefix) def get_matches(self, line, point, prefix, suffix): self._populate_matches(prefix) return self.matches def complete(self, text, state): if state == 0: self._populate_matches(text) if state < len(self.matches): return self.matches[state] else: return None class NoneCompleter(): def get_matches(self, line, point, prefix, suffix): return [] def complete(self, text, state): return None class ListCompleter(): def __init__(self, completions): self.completions = completions self.matches = [] def _populate_matches(self, prefix): self.matches = [ans for ans in self.completions if ans.startswith(prefix)] def get_matches(self, line, point, prefix, suffix): # This implementation is reliant on bash behavior that ':' is # treated as a word separator for determining prefix if get_last_pos_of_char(' ', line) != point - 1: prefix = split_unescaped(' ', line[:point])[-1] self._populate_matches(prefix) if prefix.rfind(':') != -1: for i in range(len(self.matches)): self.matches[i] = self.matches[i][self.matches[i].rfind(':') + 1:] return self.matches def complete(self, text, state): if state == 0: self._populate_matches(text) if state < len(self.matches): return self.matches[state] else: return None class MultiCompleter(): def __init__(self, completers): self.completers = completers self.matches = [] def _populate_matches(self, prefix): self.matches = [] for completer in self.completers: self.matches += completer.get_matches('', 0, prefix, '') return self.matches def get_matches(self, line, point, prefix, suffix): # This implementation assumes the get_matches will handle any # special word separation self.matches = [] for completer in self.completers: self.matches += completer.get_matches(line, point, prefix, suffix) return self.matches def complete(self, text, state): if state == 0: self._populate_matches(text) if state < len(self.matches): return self.matches[state] else: return None class InstanceTypesCompleter(): InstanceTypeSpec = namedtuple('InstanceTypeSpec', ('Name', 'CPU_Cores', 'Memory_GiB', 'Storage_GB')) GpuInstanceTypeSpec = namedtuple('GpuInstanceTypeSpec', ('Name', 'CPU_Cores', 'Memory_GiB', 'Storage_GB', 'GPU', 'GPU_Memory_GiB')) FpgaInstanceTypeSpec = namedtuple('FpgaInstanceTypeSpec', ('Name', 'CPU_Cores', 'Memory_GiB', 'Storage_GB', 'FPGA')) # AWS aws_preferred_instance_types = OrderedDict() for i in (InstanceTypeSpec('mem1_ssd1_v2_x2', 2, 4.0, 50), InstanceTypeSpec('mem1_ssd1_v2_x4', 4, 8.0, 100), InstanceTypeSpec('mem1_ssd1_v2_x8', 8, 16.0, 200), InstanceTypeSpec('mem1_ssd1_v2_x16', 16, 32.0, 400), InstanceTypeSpec('mem1_ssd1_v2_x36', 36, 72.0, 900), InstanceTypeSpec('mem1_ssd1_v2_x72', 72, 144.0, 1800), InstanceTypeSpec('mem1_ssd2_v2_x2', 2, 4.0, 160), InstanceTypeSpec('mem1_ssd2_v2_x4', 4, 8.0, 320), InstanceTypeSpec('mem1_ssd2_v2_x8', 8, 16.0, 640), InstanceTypeSpec('mem1_ssd2_v2_x16', 16, 32.0, 1280), InstanceTypeSpec('mem1_ssd2_v2_x36', 36, 72.0, 2880), InstanceTypeSpec('mem1_ssd2_v2_x72', 72, 144.0, 5760), InstanceTypeSpec('mem2_ssd1_v2_x2', 2, 8.0, 75), InstanceTypeSpec('mem2_ssd1_v2_x4', 4, 16.0, 150), InstanceTypeSpec('mem2_ssd1_v2_x8', 8, 32.0, 300), InstanceTypeSpec('mem2_ssd1_v2_x16', 16, 64.0, 600), InstanceTypeSpec('mem2_ssd1_v2_x32', 32, 128.0, 1200), InstanceTypeSpec('mem2_ssd1_v2_x48', 48, 144.0, 1800), InstanceTypeSpec('mem2_ssd1_v2_x64', 64, 256.0, 2400), InstanceTypeSpec('mem2_ssd1_v2_x96', 96, 384.0, 3600), InstanceTypeSpec('mem2_ssd2_v2_x2', 2, 8.0, 160), InstanceTypeSpec('mem2_ssd2_v2_x4', 4, 16.0, 320), InstanceTypeSpec('mem2_ssd2_v2_x8', 8, 32.0, 640), InstanceTypeSpec('mem2_ssd2_v2_x16', 16, 64.0, 1280), InstanceTypeSpec('mem2_ssd2_v2_x32', 32, 128.0, 2560), InstanceTypeSpec('mem2_ssd2_v2_x48', 48, 192.0, 3840), InstanceTypeSpec('mem2_ssd2_v2_x64', 64, 256.0, 5120), InstanceTypeSpec('mem2_ssd2_v2_x96', 96, 384.0, 7480), InstanceTypeSpec('mem3_ssd1_v2_x2', 2, 16.0, 75), InstanceTypeSpec('mem3_ssd1_v2_x4', 4, 32.0, 150), InstanceTypeSpec('mem3_ssd1_v2_x8', 8, 64.0, 300), InstanceTypeSpec('mem3_ssd1_v2_x16', 16, 128.0, 600), InstanceTypeSpec('mem3_ssd1_v2_x32', 32, 256.0, 1200), InstanceTypeSpec('mem3_ssd1_v2_x48', 48, 384.0, 1800), InstanceTypeSpec('mem3_ssd1_v2_x64', 64, 512.0, 3200), InstanceTypeSpec('mem3_ssd1_v2_x96', 96, 768.0, 3600), InstanceTypeSpec('mem3_ssd2_v2_x2', 2, 15.25, 475), InstanceTypeSpec('mem3_ssd2_v2_x4', 4, 30.5, 950), InstanceTypeSpec('mem3_ssd2_v2_x8', 8, 61.0, 1900), InstanceTypeSpec('mem3_ssd2_v2_x16', 16, 122.0, 3800), InstanceTypeSpec('mem3_ssd2_v2_x32', 32, 244.0, 7600), InstanceTypeSpec('mem3_ssd2_v2_x64', 64, 488.0, 15200), InstanceTypeSpec('mem3_ssd3_x2', 2, 16.0, 1250), InstanceTypeSpec('mem3_ssd3_x4', 4, 32.0, 2500), InstanceTypeSpec('mem3_ssd3_x8', 8, 64.0, 5000), InstanceTypeSpec('mem3_ssd3_x12', 12, 96.0, 7500), InstanceTypeSpec('mem3_ssd3_x24', 24, 192.0, 15000), InstanceTypeSpec('mem3_ssd3_x48', 48, 384.0, 30000), InstanceTypeSpec('mem3_ssd3_x96', 96, 768.0, 60000), InstanceTypeSpec('mem4_ssd1_x128', 128, 1952.0, 3840)): aws_preferred_instance_types[i.Name] = i # Azure azure_preferred_instance_types = OrderedDict() for i in (InstanceTypeSpec('azure:mem1_ssd1_x2', 2, 3.9, 32), InstanceTypeSpec('azure:mem1_ssd1_x4', 4, 7.8, 64), InstanceTypeSpec('azure:mem1_ssd1_x8', 8, 15.7, 128), InstanceTypeSpec('azure:mem1_ssd1_x16', 16, 31.4, 256), InstanceTypeSpec('azure:mem2_ssd1_x1', 1, 3.5, 128), InstanceTypeSpec('azure:mem2_ssd1_x2', 2, 7.0, 128), InstanceTypeSpec('azure:mem2_ssd1_x4', 4, 14.0, 128), InstanceTypeSpec('azure:mem2_ssd1_x8', 8, 28.0, 256), InstanceTypeSpec('azure:mem2_ssd1_x16', 16, 56.0, 512), InstanceTypeSpec('azure:mem3_ssd1_x2', 2, 14.0, 128), InstanceTypeSpec('azure:mem3_ssd1_x4', 4, 28.0, 128), InstanceTypeSpec('azure:mem3_ssd1_x8', 8, 56.0, 256), InstanceTypeSpec('azure:mem3_ssd1_x16', 16, 112.0, 512), InstanceTypeSpec('azure:mem3_ssd1_x20', 20, 140.0, 640), InstanceTypeSpec('azure:mem4_ssd1_x2', 2, 28.0, 128), InstanceTypeSpec('azure:mem4_ssd1_x4', 4, 56.0, 128), InstanceTypeSpec('azure:mem4_ssd1_x8', 8, 112.0, 256), InstanceTypeSpec('azure:mem4_ssd1_x16', 16, 224.0, 512), InstanceTypeSpec('azure:mem4_ssd1_x32', 32, 448.0, 1024), InstanceTypeSpec('azure:mem5_ssd2_x64', 64, 1792.0, 8192), InstanceTypeSpec('azure:mem5_ssd2_x128', 128, 3892.0, 16384)): azure_preferred_instance_types[i.Name] = i gpu_instance_types = OrderedDict() for i in (GpuInstanceTypeSpec('mem2_ssd1_gpu_x16', 16, 64.0, 225, '1 NVIDIA T4', 16.0), GpuInstanceTypeSpec('mem2_ssd1_gpu_x32', 32, 128.0, 900, '1 NVIDIA T4', 16.0), GpuInstanceTypeSpec('mem2_ssd1_gpu1_x32', 32, 128.0, 900, '1 NVIDIA T4', 16.0), GpuInstanceTypeSpec('mem2_ssd1_gpu_x64', 64, 256.0, 900, '1 NVIDIA T4', 16.0), GpuInstanceTypeSpec('mem2_ssd1_gpu1_x64', 64, 256.0, 900, '1 NVIDIA T4', 16.0), GpuInstanceTypeSpec('mem2_ssd1_gpu_x48', 48, 192.0, 900, '4 NVIDIA T4', 64.0), GpuInstanceTypeSpec('mem2_ssd1_gpu4_x48', 48, 192.0, 900, '4 NVIDIA T4', 64.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_x4', 4, 16.0, 250, '1 NVIDIA A10G', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_x8', 8, 32.0, 450, '1 NVIDIA A10G', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_x16', 16, 64.0, 600, '1 NVIDIA A10G', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_x32', 32, 128.0, 900, '1 NVIDIA A10G', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_x64', 64, 256.0, 1900, '1 NVIDIA A10G', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu4_x48', 48, 192.0, 3800, '4 NVIDIA A10G', 96.0), GpuInstanceTypeSpec('mem2_ssd2_gpu4_x96', 96, 384.0, 3800, '4 NVIDIA A10G', 96.0), GpuInstanceTypeSpec('mem2_ssd2_gpu8_x192', 192, 768.0, 7600, '8 NVIDIA A10G', 192.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_v2_x4', 4, 16.0, 250, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_v2_x8', 8, 32.0, 450, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_v2_x16', 16, 64.0, 600, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_v2_x32', 32, 128.0, 900, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu1_v2_x64', 64, 256.0, 1880, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem2_ssd2_gpu4_v2_x48', 48, 192.0, 3760, '4 NVIDIA L4', 96.0), GpuInstanceTypeSpec('mem2_ssd2_gpu4_v2_x96', 96, 384.0, 3760, '4 NVIDIA L4', 96.0), GpuInstanceTypeSpec('mem2_ssd2_gpu8_v2_x192', 192, 768.0, 7520, '8 NVIDIA L4', 192.0), GpuInstanceTypeSpec('mem3_ssd1_gpu1_x16', 16, 128.0, 600, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem3_ssd1_gpu1_x32', 32, 256.0, 900, '1 NVIDIA L4', 24.0), GpuInstanceTypeSpec('mem3_ssd1_gpu_x8', 8, 61.0, 160, '1 NVIDIA V100', 16.0), GpuInstanceTypeSpec('mem3_ssd1_gpu_x32', 32, 244.0, 640, '4 NVIDIA V100', 64.0), GpuInstanceTypeSpec('mem3_ssd1_gpu_x64', 64, 488.0, 1280, '8 NVIDIA V100', 128.0), GpuInstanceTypeSpec('azure:mem3_ssd2_gpu4_x64', 64, 488.0, 2048, '4 NVIDIA V100', 64.0)): gpu_instance_types[i.Name] = i fpga_instance_types = OrderedDict() for i in (FpgaInstanceTypeSpec('mem3_ssd2_fpga1_x24', 24, 256.0, 940, 1), FpgaInstanceTypeSpec('mem3_ssd2_fpga1_x48', 48, 512.0, 1880, 2), FpgaInstanceTypeSpec('mem3_ssd2_fpga1_x192', 192, 2048.0, 7520, 8)): fpga_instance_types[i.Name] = i aws_other_instance_types = OrderedDict() for i in (InstanceTypeSpec('mem1_ssd1_x2', 2, 3.8, 32), InstanceTypeSpec('mem1_ssd1_x4', 4, 7.5, 80), InstanceTypeSpec('mem1_ssd1_x8', 8, 15.0, 160), InstanceTypeSpec('mem1_ssd1_x16', 16, 30.0, 320), InstanceTypeSpec('mem1_ssd1_x32', 32, 60.0, 640), InstanceTypeSpec('mem1_ssd2_x2', 2, 3.8, 160), InstanceTypeSpec('mem1_ssd2_x4', 4, 7.5, 320), InstanceTypeSpec('mem1_ssd2_x8', 8, 15.0, 640), InstanceTypeSpec('mem1_ssd2_x16', 16, 30.0, 1280), InstanceTypeSpec('mem1_ssd2_x36', 36, 60.0, 2880), InstanceTypeSpec('mem2_ssd1_x2', 2, 7.5, 32), InstanceTypeSpec('mem2_ssd1_x4', 4, 15.0, 80), InstanceTypeSpec('mem2_ssd1_x8', 8, 30.0, 160), InstanceTypeSpec('mem2_ssd2_x2', 2, 8.0, 160), InstanceTypeSpec('mem2_ssd2_x4', 4, 16.0, 320), InstanceTypeSpec('mem2_ssd2_x8', 8, 32.0, 1280), InstanceTypeSpec('mem2_ssd2_x16', 16, 64.0, 2560), InstanceTypeSpec('mem2_ssd2_x40', 40, 160.0, 3200), InstanceTypeSpec('mem2_ssd2_x64', 64, 256.0, 5120), InstanceTypeSpec('mem3_ssd1_x2', 2, 15.0, 32), InstanceTypeSpec('mem3_ssd1_x4', 4, 30.5, 80), InstanceTypeSpec('mem3_ssd1_x8', 8, 61.0, 160), InstanceTypeSpec('mem3_ssd1_x16', 16, 122.0, 320), InstanceTypeSpec('mem3_ssd1_x32', 32, 244.0, 640), InstanceTypeSpec('mem3_ssd2_x4', 4, 30.5, 800), InstanceTypeSpec('mem3_ssd2_x8', 8, 61.0, 1600), InstanceTypeSpec('mem3_ssd2_x16', 16, 122.0, 3200), InstanceTypeSpec('mem3_ssd2_x32', 32, 244.0, 6400), InstanceTypeSpec('mem1_hdd1_x2', 2, 3.75, 200), InstanceTypeSpec('mem1_hdd1_x4', 4, 7.5, 400), InstanceTypeSpec('mem1_hdd1_x8', 8, 15.0, 800), InstanceTypeSpec('mem1_hdd1_x16', 16, 30.0, 1600), InstanceTypeSpec('mem1_hdd1_x36', 36, 60.0, 3200), InstanceTypeSpec('mem1_hdd1_v2_x2', 2, 4.0, 200), InstanceTypeSpec('mem1_hdd1_v2_x4', 4, 8.0, 400), InstanceTypeSpec('mem1_hdd1_v2_x8', 8, 16.0, 800), InstanceTypeSpec('mem1_hdd1_v2_x16', 16, 32.0, 1600), InstanceTypeSpec('mem1_hdd1_v2_x36', 36, 72.0, 3600), InstanceTypeSpec('mem1_hdd1_v2_x72', 72, 144.0, 7200), InstanceTypeSpec('mem1_hdd1_v2_x96', 96, 192.0, 9600), InstanceTypeSpec('mem1_hdd2_x1', 1, 1.7, 160), InstanceTypeSpec('mem1_hdd2_x8', 8, 7.0, 1680), InstanceTypeSpec('mem1_hdd2_x32', 32, 60.5, 3360), InstanceTypeSpec('mem2_hdd2_x1', 1, 3.8, 410), InstanceTypeSpec('mem2_hdd2_x2', 2, 7.5, 840), InstanceTypeSpec('mem2_hdd2_x4', 4, 15.0, 1680), InstanceTypeSpec('mem2_hdd2_v2_x2', 2, 8.0, 1000), InstanceTypeSpec('mem2_hdd2_v2_x4', 4, 16.0, 2000), InstanceTypeSpec('mem3_hdd2_v2_x2', 2, 16.0, 500), InstanceTypeSpec('mem3_hdd2_v2_x4', 4, 32.0, 1000), InstanceTypeSpec('mem3_hdd2_v2_x8', 8, 64.0, 2000)): aws_other_instance_types[i.Name] = i default_instance_type = aws_preferred_instance_types['mem1_ssd1_v2_x4'] standard_instance_types = OrderedDict() standard_instance_types.update(aws_preferred_instance_types) standard_instance_types.update(azure_preferred_instance_types) standard_instance_types.update(aws_other_instance_types) instance_types = OrderedDict() instance_types.update(standard_instance_types) instance_types.update(gpu_instance_types) instance_types.update(fpga_instance_types) instance_type_names = instance_types.keys() def complete(self, text, state): try: return self.instance_type_names[state] except IndexError: return None def __call__(self, prefix, parsed_args, **kwargs): return [name for name in self.instance_type_names if name.startswith(prefix)]