Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit fb5cd3c

Browse files
authored
Merge pull request #26 from juanjux/fix/empty_code_fatal
Fix/empty code fatal
2 parents 56f549a + 89a0205 commit fb5cd3c

File tree

2 files changed

+47
-49
lines changed

2 files changed

+47
-49
lines changed

native/python_package/python_driver/requestprocessor.py

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pydetector import detector
55
from traceback import format_exc
66
from 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...:
1010
AnyStr = Any
@@ -22,11 +22,11 @@
2222
Request = NewType('Request', Dict[str, Any])
2323
Response = 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())

native/python_package/test/test_python_driver.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
sys.path.append('..')
1818
from python_driver import __version__, get_processor_instance
1919
from python_driver.requestprocessor import (
20-
Request, Response, RequestProcessorJSON,
21-
InBuffer, RequestCheckException
22-
)
20+
Request, Response, RequestProcessorJSON, InBuffer, EmptyCodeException)
2321

2422
if TEST_MSGPACK:
2523
from python_driver.requestprocessor import RequestProcessorMSGPack
@@ -251,16 +249,16 @@ def test_10_check_input(self) -> None:
251249
self._restart_data('json')
252250
brequest = convert_bytes(self.data, to_bytes=True)
253251
processor = RequestProcessorMSGPack(self.recvbuffer)
254-
res = processor._check_input_request(brequest)
252+
res = processor._parse_input_request(brequest)
255253
self.assertEqual(res[1], 'test.py')
256254

257255
def test_20_check_input_bad(self) -> None:
258256
self._restart_data('msgpack')
259257
del self.data['content']
260258
brequest = convert_bytes(self.data, to_bytes=True)
261259
processor = RequestProcessorMSGPack(self.recvbuffer)
262-
with self.assertRaises(RequestCheckException) as _: # noqa: F841
263-
processor._check_input_request(brequest)
260+
with self.assertRaises(EmptyCodeException) as _: # noqa: F841
261+
processor._parse_input_request(brequest)
264262

265263
def test_30_send_response_msgpack(self) -> None:
266264
self._restart_data('msgpack')
@@ -290,6 +288,7 @@ def test_50_return_error(self) -> None:
290288
self.assertDictEqual(res[0] , {'driver': 'python23:%s' % __version__,
291289
'errors': ['test error'],
292290
'filepath': 'test.py',
291+
'ast': None,
293292
'status': 'fatal'})
294293

295294

0 commit comments

Comments
 (0)