Skip to content

Commit

Permalink
Progress with assistant
Browse files Browse the repository at this point in the history
  • Loading branch information
aivarannamaa committed Aug 30, 2018
1 parent feb78a9 commit 6086043
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 55 deletions.
3 changes: 3 additions & 0 deletions misc/turtle_katse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from turtle import forward

forward(100)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
jedi>=0.10
setuptools>=33.1.1
pyserial>=3.2.1
pylint>=1.6.5
astroid>=1.4.9
82 changes: 54 additions & 28 deletions thonny/assistance.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def _handle_toplevel_response(self, msg: ToplevelResponse) -> None:
self._clear()

# prepare for snapshot
key = msg.get("filename", "<shell>")
key = msg.get("filename", "<pyshell>")
self._current_snapshot = {
"timestamp" : datetime.datetime.now().isoformat()[:19],
"main_file_path" : key,
Expand All @@ -98,11 +98,15 @@ def _handle_toplevel_response(self, msg: ToplevelResponse) -> None:
if msg.get("user_exception"):
self._explain_exception(msg["user_exception"])

if msg.get("filename"):
if msg.get("filename") and os.path.exists(msg["filename"]):
self.main_file_path = msg["filename"]
source = read_source(msg["filename"])
self._start_program_analyses(msg["filename"],
source,
_get_imported_user_files(msg["filename"], source))
else:
self.main_file_path = None
self._present_conclusion()


def _explain_exception(self, error_info):
Expand All @@ -113,7 +117,8 @@ def _explain_exception(self, error_info):
+ rst_utils.escape(error_info["message"]))
+ "\n")

if error_info.get("lineno") is not None:
if (error_info.get("lineno") is not None
and os.path.exists(error_info["filename"])):
rst += (
"`%s, line %d <%s>`__\n\n" % (
os.path.basename(error_info["filename"]),
Expand Down Expand Up @@ -148,14 +153,15 @@ def _explain_exception(self, error_info):
# use relevance 1 only when there is nothing better
relevance_threshold = 1

suggestions = [s for s in suggestions if s.relevance >= relevance_threshold]

for i, suggestion in enumerate(suggestions):
if suggestion.relevance >= relevance_threshold :
rst += self._format_suggestion(suggestion,
i==len(suggestions)-1,
# TODO: is it good if first is preopened?
# It looks cleaner if it is not.
False #i==0
)
rst += self._format_suggestion(suggestion,
i==len(suggestions)-1,
# TODO: is it good if first is preopened?
# It looks cleaner if it is not.
False #i==0
)

self._current_snapshot["exception_suggestions"] = [
dict(sug._asdict()) for sug in suggestions
Expand Down Expand Up @@ -200,7 +206,7 @@ def _start_program_analyses(self, main_file_path, main_file_source,

for cls in _program_analyzer_classes:
analyzer = cls(self._accept_warnings)
analyzer.start_analysis({main_file_path} | set(imported_file_paths))
analyzer.start_analysis(main_file_path, imported_file_paths)
self._analyzer_instances.append(analyzer)

self._append_text("\nAnalyzing your code ...", ("em",))
Expand All @@ -218,21 +224,34 @@ def _accept_warnings(self, analyzer, warnings):

self._accepted_warning_sets.append(warnings)
if len(self._accepted_warning_sets) == len(self._analyzer_instances):
# all providers have reported
all_warnings = [w for ws in self._accepted_warning_sets for w in ws]
self._present_warnings(all_warnings)
self._present_warnings()
self._present_conclusion()

def _present_conclusion(self):

if not self.text.get("1.0", "end").strip():
if (self.main_file_path is not None
and os.path.exists(self.main_file_path)):
self._append_text("\n")
self.text.append_rst("The code in `%s <%s>`__ looks good.\n\n"
% (os.path.basename(self.main_file_path),
self._format_file_url({"filename" : self.main_file_path})))
self.text.append_rst("If it is not working as it should, "
+ "then consider using some general "
+ "`debugging techniques <thonny-help://debugging#sss>`__.\n\n",
("em",))


if self.text.get("1.0", "end").strip():
self._append_feedback_link()

def _present_warnings(self, warnings):
def _present_warnings(self):
warnings = [w for ws in self._accepted_warning_sets for w in ws]
self.text.direct_delete("end-2l linestart", "end-1c lineend")

if not warnings:
return

#self._append_text("\n")
# TODO: show filename when more than one file was analyzed
# Put main file first
# TODO: group by file and confidence
rst = (
".. default-role:: code\n"
+ "\n"
Expand All @@ -249,7 +268,7 @@ def _present_warnings(self, warnings):
for filename in by_file:
rst += "`%s <%s>`__\n\n" % (os.path.basename(filename),
self._format_file_url(dict(filename=filename)))
file_warnings = sorted(by_file[filename], key=lambda x: x["lineno"])
file_warnings = sorted(by_file[filename], key=lambda x: x.get("lineno", 0))
for i, warning in enumerate(file_warnings):
rst += (
self._format_warning(warning, i == len(file_warnings)-1)
Expand All @@ -269,7 +288,8 @@ def _format_warning(self, warning, last):
title = rst_utils.escape(warning["msg"].splitlines()[0])
if warning.get("lineno") is not None:
url = self._format_file_url(warning)
title = "`Line %d <%s>`__ : %s" % (warning["lineno"], url, title)
if warning.get("lineno"):
title = "`Line %d <%s>`__ : %s" % (warning["lineno"], url, title)

if warning.get("explanation_rst"):
explanation_rst = warning["explanation_rst"]
Expand Down Expand Up @@ -297,7 +317,7 @@ def _append_feedback_link(self):

def _format_file_url(self, atts):
assert atts["filename"]
s = "thonny://" + rst_utils.escape(atts["filename"])
s = "thonny-editor://" + rst_utils.escape(atts["filename"])
if atts.get("lineno") is not None:
s += "#" + str(atts["lineno"])
if atts.get("col_offset") is not None:
Expand All @@ -313,7 +333,7 @@ def _ask_feedback(self, event=None):
snapshots = all_snapshots

ui_utils.show_dialog(FeedbackDialog(get_workbench(),
self._current_snapshot["main_file_path"],
self.main_file_path,
snapshots))

class AssistantRstText(rst_utils.RstText):
Expand Down Expand Up @@ -392,8 +412,9 @@ def __init__(self, error_info):
class ProgramAnalyzer:
def __init__(self, on_completion):
self.completion_handler = on_completion
self.cancelled = False

def start_analysis(self, filenames):
def start_analysis(self, main_file_path, imported_file_paths):
raise NotImplementedError()

def cancel_analysis(self):
Expand All @@ -403,7 +424,6 @@ class SubprocessProgramAnalyzer(ProgramAnalyzer):
def __init__(self, on_completion):
super().__init__(on_completion)
self._proc = None
self.cancelled = False

def cancel_analysis(self):
self.cancelled = True
Expand Down Expand Up @@ -438,8 +458,8 @@ def __init__(self, master, main_file_path, all_snapshots):

intro_label = ttk.Label(self,
text="Below are the messages Assistant gave you in response to "
+ ("using the shell" if main_file_path == "<shell>"
else "testing '" + os.path.basename(main_file_path)) + "'"
+ ("using the shell" if self._happened_in_shell()
else "testing '" + os.path.basename(main_file_path) + "'")
+ " since " + self._get_since_str()
+ ".\n\n"
+ "In order to improve this feature, Thonny developers would love to know how "
Expand Down Expand Up @@ -528,6 +548,12 @@ def __init__(self, master, main_file_path, all_snapshots):
self._checked_box = "[X]"
self._populate_tree()

def _happened_in_shell(self):
return (
self.main_file_path is None
or self.main_file_path.lower() == "<pyshell>"
)

def _populate_tree(self):
groups = {}

Expand Down Expand Up @@ -763,7 +789,7 @@ def _get_imported_user_files(main_file, source=None):
for item in node.names:
module_names.add(item.name)
elif isinstance(node, ast.ImportFrom):
module_names.add(node.name)
module_names.add(node.module)

imported_files = set()

Expand Down
3 changes: 2 additions & 1 deletion thonny/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2180,7 +2180,8 @@ def _get_python_version_string(add_word_size=False):
return result

def _fetch_frame_source_info(frame):
if frame.f_code.co_filename is None:
if (frame.f_code.co_filename is None
or not os.path.exists(frame.f_code.co_filename)):
return None, None, True

if frame.f_code.co_name == "<module>":
Expand Down
8 changes: 7 additions & 1 deletion thonny/plugins/help/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ def __init__(self, master):
)

self.load_rst_file("help.rst")


def load_topic(self, topic, fragment=None):
self.load_rst_file(topic + ".rst")
# TODO: scroll to fragment

def load_rst_file(self, filename):
self.text.clear()

if not os.path.isabs(filename):
filename = os.path.join(os.path.dirname(__file__), filename)

Expand Down
4 changes: 4 additions & 0 deletions thonny/plugins/help/debugging.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Debugging
=========

TODO:
11 changes: 6 additions & 5 deletions thonny/plugins/mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@
from thonny.running import get_frontend_python


class MyPyChecker(SubprocessProgramAnalyzer):
class MyPyAnalyzer(SubprocessProgramAnalyzer):

def start_analysis(self, filenames: Iterable[str]) -> None:
def start_analysis(self, main_file_path, imported_file_paths: Iterable[str]) -> None:

args = [get_frontend_python(), "-m",
"mypy",
"--ignore-missing-imports",
"--check-untyped-defs",
"--warn-redundant-casts",
"--show-column-numbers"
] + list(filenames)
"--show-column-numbers",
main_file_path
] + list(imported_file_paths)

# TODO: ignore "... need type annotation" messages

Expand Down Expand Up @@ -77,4 +78,4 @@ def _parse_and_output_warnings(self, pylint_proc):
self.completion_handler(self, warnings)

def load_plugin():
add_program_analyzer(MyPyChecker)
add_program_analyzer(MyPyAnalyzer)
23 changes: 18 additions & 5 deletions thonny/plugins/pylint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@
from thonny.running import get_frontend_python


class PylintChecker(SubprocessProgramAnalyzer):
class PylintAnalyzer(SubprocessProgramAnalyzer):

def start_analysis(self, filenames):
def start_analysis(self, main_file_path, imported_file_paths):
relevant_symbols = {key for key in all_checks_by_symbol if all_checks_by_symbol[key]["usage"] == "warning"}

if 'bad-python3-import' in relevant_symbols:
# https://github.com/PyCQA/pylint/issues/2453
# TODO: allow if this is fixed in current version
relevant_symbols.remove('bad-python3-import')

ignored_modules = {
"turtle" # has dynamically generated attributes
}


self._proc = ui_utils.popen_with_ui_thread_callback(
[get_frontend_python(), "-m",
"pylint",
Expand All @@ -20,11 +31,13 @@ def start_analysis(self, filenames):
#"--disable=missing-docstring,invalid-name,trailing-whitespace,trailing-newlines,missing-final-newline,locally-disabled,suppressed-message",
"--disable=all",
"--enable=" + ",".join(relevant_symbols),
"--ignored-modules=" + ",".join(ignored_modules),
"--max-line-length=120",
"--output-format=text",
"--reports=n",
"--msg-template={{'filename':{abspath!r}, 'lineno':{line}, 'col_offset':{column}, 'symbol':{symbol!r}, 'msg':{msg!r}, 'msg_id':{msg_id!r}}}"
] + list(filenames),
"--msg-template={{'filename':{abspath!r}, 'lineno':{line}, 'col_offset':{column}, 'symbol':{symbol!r}, 'msg':{msg!r}, 'msg_id':{msg_id!r}}}",
main_file_path
] + list(imported_file_paths),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
Expand Down Expand Up @@ -2767,4 +2780,4 @@ def _parse_and_output_warnings(self, pylint_proc):
all_checks_by_symbol = {c["msg_sym"] : c for c in all_checks}

def load_plugin():
add_program_analyzer(PylintChecker)
add_program_analyzer(PylintAnalyzer)
26 changes: 26 additions & 0 deletions thonny/plugins/pylint_checkers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import astroid

from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker

class FileNamingChecker(BaseChecker):
__implements__ = IAstroidChecker

name = 'file-naming'
priority = -1
msgs = {
'W7401': (
'Script shadows a library module.',
'non-unique-returns',
'All constants returned in a function should be unique.'
),
}
options = (
(
'ignore-ints',
{
'default': False, 'type': 'yn', 'metavar' : '<y_or_n>',
'help': 'Allow returning non-unique integers',
}
),
)
3 changes: 2 additions & 1 deletion thonny/plugins/stdlib_error_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ def _sug_local_from_global(self):
relevance = 0
body = None

if self.last_frame.code_name == "<module>":
if (self.last_frame.code_name == "<module>"
and self.last_frame_module_ast is not None):
function_names = set()
for node in ast.walk(self.last_frame_module_ast):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
Expand Down
Loading

0 comments on commit 6086043

Please sign in to comment.