pythonvisual-studio-codejupyter-notebookpylancepython-jedi

Ensuring VSCode Python Autocompletion


How can I ensure that when I instantiate a data structure in VSCode+Jupyter+Python, the attributes of the data structure are available for autocompletion throughout the notebook.


# %% Jupyter Cell #1
#This cell is executed before attempting autocompletes in cell 2
@dataclass
class ExistingItemNames:
    pass

class SearchableItemNames:
    def __init__(self, var_names:list):
        self.names__ = ExistingItemNames() 
        for name in var_names:
            setattr(self.names__, name, name)
        self.names = self.names__.__dict__

si = SearchableItemNames([f"v{i}" for i in range(2000)])

# %% Jupyter Cell 2

#outside other data structures, accessing through a 
# dict or attr seem equivalent
si.names['v1999'] #does not find 'v1999' key via autocomplete
si.names['v10'] # does find 'v10' key via autocomplete
si.names__.v1999 #does not find `v1999` attr via autocomplete
si.names__.v10 #does find `v10` attr via autocomplete

#inside of a data structure, the dict is required for autocompletion
# but still does not find all values
(si.names__.v1999) #does not find `v1999` attr via autocomplete
(si.names__.v10) #does not find `v10` attr via autocomplete
(si.names['v1999']) #does not find 'v1999' key via autocomplete
(si.names['v10']) # does find 'v10' key via autocomplete

I understand that exhaustive enumeration of keys or attr would not be a good solution for all use cases due to the limitations of the python language server, but is there a way I can force the IDE (VSCode+Jupyter) to only do so for certain objects, within certain python envs, or certain Jupyter notebooks?


Solution

  • Based on the comments I put this together. The idea is that in order to get the IDE to autocomplete all of a classes attr, we need to hardcode the attr. We can dynamically accomplish the hardcoding by composing a .py file containing a dataclass using str ops, then writing the .py file to the same wd as the .ipynb. Once the .py file exists, we can load the composed SearableItems object into the .ipynb and the IDE will autocomplete all of its attr. Would love to see a more pythonic way of accomplishing this, as this code feels a bit unholy to me, but it does work very well for my purposes.

    # %% Jupyter Cell 1
    tmp_dict = {f"var_{i}":f'{i}' for i in range(2000)}
    
    str_nums = {1:'one', 2:'two',
                3:'three', 4:'four',
                5:'five', 6:'six',
                7:'seven', 8:'eight',
                9:'nine'}
    
    def write_searchable_items_py(items_dict, fname = 'searchable_items.py'):
        if '.py' not in fname:
            fname = fname + '.py'
            
        dataclass_str = [f"""from dataclasses import dataclass, field
    
    def get_vals():
        return {items_dict}
        
    @dataclass
    class SearchableItems:""", ]
    
        lines = []
        for k in items_dict:
            val = items_dict[k]
            if k == '#':
                k = f"pound_hashtag" 
            k = k.replace(" ", "_")
            k = ''.join(c for c in k if c.isalnum() or c == '_')
            if k[0].isnumeric():
                
                if int(k[0]) in str_nums.keys():
                    #k = k + str_nums[int(k[0])]
                    k = str_nums[int(k[0])] + k[1:]
                else:
                    k = "_" + k
            lines.append(f"\t{k}:str = '{val}'")
    
        dict_line = [f"\td: dict = field(default_factory = get_vals)", ]
    
        dataclass_str = "\n".join(dataclass_str + lines  + dict_line)
        with open(fname, 'w') as f:
            f.write(dataclass_str)
    
    # %% Jupyter Cell 2
    write_searchable_items_py(tmp_dict)
    
    # %% Jupyter Cell 3
    from searchable_items import SearchableItems
    si = SearchableItems()
    
    # %% Jupyter Cell 4
    si.var_1999 #autocompletes
    si.var_10 #autocompletes
    (si.var_1999) #autocompletes
    (si.var_10) #autocompletes
    
    si.d['var_10'] #autocompletes
    (si.d['var_10']) #autocompletes
    si.d['var_1999'] #Finds some keys like 1999, but not 1999
    (si.d['var_1999']) #Finds some keys like 1999, but not 1999