44from pydetector import detector
55from traceback import format_exc
66from python_driver .version import __version__
7- from typing import (Any , IO , NewType , Tuple , cast , List , Iterator , Dict )
7+ from typing import (Any , IO , NewType , Tuple , cast , List , Iterator , Dict , Optional )
88
99# typing.AnyStr is bugged on this version of MyPy, so...:
1010AnyStr = Any
2222Request = NewType ('Request' , Dict [str , Any ])
2323Response = NewType ('Response' , Dict [AnyStr , Any ])
2424
25- class RequestCheckException (Exception ):
25+ class EmptyCodeException (Exception ):
2626 """
27- Exception produced while there is an error during the processing
28- of the request. It will cause an error reply to be produced on the
29- output buffer.
27+ Exception produced when the input code is empty. This should
28+ generate an error (because we can't parse anything) but not
29+ a fatal one (e. g. empty __init__.py files)
3030 """
3131 pass
3232
@@ -67,26 +67,6 @@ def _tostr_request(self, request: RawRequest) -> Request:
6767 """
6868 pass
6969
70- def _check_input_request (self , request : RawRequest ) -> Tuple [str , str ]:
71- """
72- Check the incoming request package and validate that the 'content' and
73- 'language' keys are not missing and that 'language' and 'language_version'
74- are the right ones for this driver. It will also call _preprare_dict
75- to covnvert the request bytestrings to str.
76-
77- :param request: The incoming request, already deserialized.
78-
79- .. raises::
80- RequestCheckException if the request failed to validate.
81- """
82- str_request = self ._tostr_request (request )
83- code = asstr (str_request .get ('content' , '' ))
84-
85- if not code :
86- raise RequestCheckException ('Bad input message, missing content' )
87-
88- return code , asstr (str_request .get ('filepath' , '' ))
89-
9070 @abc .abstractmethod
9171 def _send_response (self , response : Response ) -> None :
9272 """
@@ -99,7 +79,8 @@ def _send_response(self, response: Response) -> None:
9979 """
10080 pass
10181
102- def _return_error (self , filepath : AnyStr = '' , status : AnyStr = 'error' ) -> None :
82+ def _return_error (self , filepath : AnyStr = '' , status : AnyStr = 'error' ,
83+ ast : Optional [Dict [Any , Any ]] = None ) -> None :
10384 """
10485 Build and send to stdout and error response. Also log
10586 the errors to the python_driver.log.
@@ -109,11 +90,13 @@ def _return_error(self, filepath: AnyStr='', status: AnyStr='error') -> None:
10990 :param status: error type, 'error' or 'fatal'
11091 """
11192
93+ ret_ast = None if status == 'fatal' else ast
11294 logging .error ('Filepath: {}, Errors: {}' .format (filepath , self .errors ))
11395 response = Response ({
11496 'status' : status ,
11597 'errors' : self .errors ,
11698 'driver' : 'python23:%s' % __version__ ,
99+ 'ast' : ret_ast ,
117100 })
118101 if filepath :
119102 response ['filepath' ] = filepath
@@ -134,24 +117,35 @@ def process_request(self, request: RawRequest) -> None:
134117 self .errors = []
135118
136119 try :
137- code , filepath = self ._check_input_request (request )
138-
139- # We want the code detection to be fast and we prefer Python3 AST so using
140- # the stop_on_ok_ast will avoid running a Python2 subprocess to check the
141- # AST with Python2 if the Python3 version (which is a better AST anyway) worked
142- resdict = detector .detect (codestr = code , stop_on_ok_ast = True )
143- codeinfo = resdict ['<code_string>' ]
144- version = codeinfo ['version' ]
145-
146- if version in (3 , 6 ) and codeinfo ['py3ast' ]:
147- ast = codeinfo ['py3ast' ]
148- elif version in (1 , 2 ) and codeinfo ['py2ast' ]:
149- ast = codeinfo ['py2ast' ]
120+ str_request = self ._tostr_request (request )
121+ code = asstr (str_request .get ('content' , '' ))
122+
123+ if code :
124+ # We want the code detection to be fast and we prefer Python3 AST so using
125+ # the stop_on_ok_ast will avoid running a Python2 subprocess to check the
126+ # AST with Python2 if the Python3 version (which is a better AST anyway) worked
127+ resdict = detector .detect (codestr = code , stop_on_ok_ast = True )
128+ codeinfo = resdict ['<code_string>' ]
129+ version = codeinfo ['version' ]
130+
131+ if version in (3 , 6 ) and codeinfo ['py3ast' ]:
132+ ast = codeinfo ['py3ast' ]
133+ elif version in (1 , 2 ) and codeinfo ['py2ast' ]:
134+ ast = codeinfo ['py2ast' ]
135+ else :
136+ raise Exception ('Could not determine Python version' )
137+
138+ if not ast :
139+ raise Exception ('Empty AST generated from non empty code' )
150140 else :
151- raise Exception ('Could not determine Python version' )
152-
153- if not ast :
154- raise Exception ('Empty AST generated' )
141+ # Module with empty code (like __init__.py) return a module-only AST
142+ # since this would still have semantic meaning for Python
143+ ast = {"PY3AST" : {
144+ "ast_type" : "Module" ,
145+ "lineno" : 1 ,
146+ "col_offset" : 1 ,
147+ }}
148+ version = 3
155149
156150 response = Response ({
157151 'status' : 'ok' ,
@@ -163,11 +157,16 @@ def process_request(self, request: RawRequest) -> None:
163157 'driver' : 'python23:%s' % __version__ ,
164158 }
165159 })
160+
166161 if filepath :
167162 response ['filepath' ] = filepath
168163
169164 self ._send_response (response )
170165
166+ except EmptyCodeException :
167+ self .errors .append ('Code field empty' )
168+ self ._return_error (filepath , status = 'error' , ast = ast )
169+
171170 except :
172171 status = 'fatal' if ast is None else 'error'
173172 self .errors .append (format_exc ())
0 commit comments