ENB: another playful prototye

39 views
Skip to first unread message

Edward K. Ream

unread,
Jun 11, 2019, 10:47:58 AM6/11/19
to leo-e...@googlegroups.com
I spent most of this weekend working on the `script: report missing docstrings in Leo itself` script in scripts.leo. You can open scripts.leo with <Alt-x>leo-sc<tab><return>.

This was going to be the basis of a new find-missing-docstrings command.  That won't happen, as I'll now explain. This is an Engineering Notebook post, full of geeky details.  Feel free to ignore.

g.getRepresentativeLiveObjects

This new function is the highlight of the prototype.  It's in devel, and it's going to stay.  Here it is:

def getRepresentativeLiveObjects():
   
'''
    Return a dict. Keys classes.
    Values are the first (representative) live object for each type.
    '''

    d
= {} # Keys are types, values are the *first* instance.
   
for obj in gc.get_objects():
        t
= type(obj)
       
if t not in d and hasattr(obj, '__class__'):
            d
[t] = obj
   
return d

This function is astoundingly fast.  g.get_objects returns all the live objects in Leo.  Nevertheless, this function runs in about 1/20 sec on my machine.  This was the happy surprise of the prototype.

The plan

I thought to myself.  Gee. It's really easy to get a representative live object for any (instantiated) class in Leo.  Why not use that live object to detect missing docstrings?  Well, the "report missing docstrings" script does that. As shown in #1187, its output is:

 
 7 missing docstrings in AbbrevCommandsClass
 
1 missing docstring  in AtButtonCallback
 
30 missing docstrings in AtFile
 
19 missing docstrings in AutoCompleterClass
 
2 missing docstrings in BindingInfo
 
6 missing docstrings in Bunch
 
2 missing docstrings in Chapter
 
10 missing docstrings in ChapterController
 
3 missing docstrings in CommanderCacher
 
2 missing docstrings in CommanderWrapper
113 missing docstrings in Commands
 
4 missing docstrings in ControlCommandsClass
 
3 missing docstrings in ConvertCommandsClass
 
3 missing docstrings in DebugCommandsClass
 
2 missing docstrings in DefaultDict
 
5 missing docstrings in DefaultWrapper
 
28 missing docstrings in DynamicWindow
 
39 missing docstrings in EditCommandsClass
 
11 missing docstrings in EditFileCommandsClass
 
7 missing docstrings in EvalController
 
3 missing docstrings in EventWrapper
 
3 missing docstrings in FastRead
 
39 missing docstrings in FileCommands
 
7 missing docstrings in FindTabManager
 
4 missing docstrings in FreeLayoutController
 
1 missing docstring  in GeneralSetting
 
1 missing docstring  in GetArg
 
1 missing docstring  in GlobalCacher
 
4 missing docstrings in GlobalConfigManager
 
1 missing docstring  in GoToCommands
 
6 missing docstrings in HelpCommandsClass
 
37 missing docstrings in JEditColorizer
 
49 missing docstrings in KeyHandlerClass
 
3 missing docstrings in KeyHandlerCommandsClass
 
9 missing docstrings in KeyStroke
 
6 missing docstrings in KillBufferCommandsClass
 
1 missing docstring  in KillBufferIterClass
 
13 missing docstrings in LeoApp
 
52 missing docstrings in LeoFind
 
49 missing docstrings in LeoImportCommands
 
1 missing docstring  in LeoKeyEvent
 
11 missing docstrings in LeoPluginsController
 
1 missing docstring  in LeoQComboBox
 
3 missing docstrings in LeoQTextBrowser
 
7 missing docstrings in LeoQTreeWidget
 
19 missing docstrings in LeoQtBody
 
3 missing docstrings in LeoQtEventFilter
 
68 missing docstrings in LeoQtFrame
 
23 missing docstrings in LeoQtGui
 
17 missing docstrings in LeoQtLog
 
27 missing docstrings in LeoQtMenu
 
1 missing docstring  in LeoQtSpellTab
 
50 missing docstrings in LeoQtTree
 
1 missing docstring  in LeoQtTreeTab
 
2 missing docstrings in LeoTabbedTopLevel
 
28 missing docstrings in LoadManager
 
2 missing docstrings in LocalConfigManager
 
23 missing docstrings in NullBody
 
1 missing docstring  in NullColorizer
 
64 missing docstrings in NullFrame
 
38 missing docstrings in NullGui
 
10 missing docstrings in NullIconBarClass
 
22 missing docstrings in NullLog
 
40 missing docstrings in NullMenu
 
15 missing docstrings in NullTree
 
1 missing docstring  in PersistenceDataController
 
97 missing docstrings in Position
 
1 missing docstring  in PrintingController
 
13 missing docstrings in Py_Importer
 
10 missing docstrings in QMinibufferWrapper
 
10 missing docstrings in QTextEditWrapper
 
6 missing docstrings in QtIconBarClass
 
31 missing docstrings in QtMenuWrapper
 
7 missing docstrings in QtStatusLineClass
 
1 missing docstring  in QtTabBarWrapper
 
6 missing docstrings in RecentFilesManager
 
4 missing docstrings in RectangleCommandsClass
 
6 missing docstrings in RedirectClass
 
12 missing docstrings in RstCommands
 
2 missing docstrings in ScriptingController
 
9 missing docstrings in SearchWidget
 
6 missing docstrings in ShadowController
 
3 missing docstrings in SpellCommandsClass
 
1 missing docstring  in SpellTabHandler
 
7 missing docstrings in SqlitePickleShare
 
12 missing docstrings in StringTextWrapper
 
1 missing docstring  in StyleSheetManager
 
7 missing docstrings in TabbedFrameFactory
 
61 missing docstrings in TangleCommands
 
26 missing docstrings in TestManager
 
13 missing docstrings in TypedDict
 
14 missing docstrings in TypedDictOfLists
 
70 missing docstrings in Undoer
 
73 missing docstrings in VNode
 
17 missing docstrings in VimCommands
 
4 missing docstrings in VisLineEdit
 
1 missing docstring  in leoIconBarButton
129 leo classes. methods 4370, w/o docstrings 1595 in 0.10 sec

Note the time: 1/10 second!

report-missing-docstrings is doomed

I thought it would be straightforward, if a bit tricky, to use the representative live objects to create clickable links to the offending routines.  But no. The task is impossible, for two fundamental reasons:

1. The code runs within Leo, so it can only report problems in Leo's own code.  There is no way to "recreate" the desired program under test from a Leo outline!

End of story!  We aren't going to do AI to discover apps, and no way would we want to run those apps. It would be so naive and dangerous.

In contrast, pylint takes uses the AST: it doesn't actually run anything.  A huge difference.

2. Lives objects are useful, because we can use python's inspect module to get data on them.  The script does this.  Alas, inspect knows nothing of Leo's outlines, positions, nodes, etc.  And even Leo can't recreate that data with any reasonable amount of work.  Yes, one could imagine heroic measures, but problem one isn't going away, so it's useless to solve problem two.

Looking back, the code was destined to drown, not in an inch of water, but more like 20 feet :-)

Summary

I spent almost two days on this script.  I think the time was well worth the effort. The prototype was useful because it:

- failed quickly,
- it showed why my original plan was hopeless,
- it produced g.getRepresentativeLiveObjects.

We'll end up revisiting similar issues when integrating pyzo's Shell into Leo.

Edward

vitalije

unread,
Jun 12, 2019, 7:59:26 AM6/12/19
to leo-editor
I am not sure what were your intentions for find-missing-docstrings command. I assume you found out that it is impossible to use live objects in general, but useful for searching in Leo's own code.
Here is my script that searches Leo outline for methods and classes with missing docstrings:

import timeit
def has_docstring(lines, n):
    '''returns True if function/method/class whose definition
       starts on n-th line in lines has a docstring'''
    for line in lines[n:]:
        s = line.strip()
        if not s or s.startswith('#'): continue
        return s.startswith(('"', "'"))

def is_a_definition(line):
    return line.startswith(('def ', 'class ')) and not line.partition(' ')[2].startswith('__init__')
    # I guess it is useful to skip __init__ methods because their docstring
    # is usually docstring of the class

def find_all_missing_docstrings():
    for p in c.all_positions():
        lines = p.b.split('\n')
        for i, line in enumerate(lines, start=1):
            if is_a_definition(line) and not has_docstring(lines, i):
                yield p, i
def f():
    return list(find_all_missing_docstrings())
def nlink(p, i):
    return "%s,%d"%(p.get_UNL(with_proto=True,
                              with_count=True,
                              with_index=True),i)
c.frame.log.clearTab('Log')
t1 = timeit.timeit(f, number=20)/20 * 1000
g.es('finished in %.2fms'%t1)
res =  f()
g.es(len(res))
p, i = res[3117]
lines = p.b.split('\n')
g.es(lines[i-1], nodeLink=nlink(p, i))


On my machine in my version of LeoPy.leo this script finds about 3737 missing docstrings in 122.28ms.
The script will write to Log pane 3117-th occurrence with the clickable link. I guess it would take long to write all the occurrences in the Log pane, so I've chosen one random item.

HTH Vitalije

Edward K. Ream

unread,
Jun 12, 2019, 11:50:01 AM6/12/19
to leo-editor
On Wed, Jun 12, 2019 at 6:59 AM vitalije <vita...@gmail.com> wrote:
I am not sure what were your intentions for find-missing-docstrings command.

It was mostly to play with live objects. Pylint's "missing-docstring" test creates clickable links, so for actually adding docstrings pylint is the best tool.  However, it was fun so see that a simple script discovers missing docstrings in about 1/10 sec.

I assume you found out that it is impossible to use live objects in general, but useful for searching in Leo's own code.

Exactly.
Excellent!  I'll try it out.  It looks like it will find missing docstring in any .leo file.

Edward

Edward K. Ream

unread,
Jun 12, 2019, 11:57:15 AM6/12/19
to leo-editor
On Wed, Jun 12, 2019 at 10:49 AM Edward K. Ream <edre...@gmail.com> wrote:

> On Wed, Jun 12, 2019 at 6:59 AM vitalije <vita...@gmail.com> wrote:
> On my machine in my version of LeoPy.leo this script finds about 3737 missing docstrings in 122.28ms.

Please feel free to add this script to scripts.leo.  Presumably you would disable the timeit code.

Edward
Reply all
Reply to author
Forward
0 new messages