1import itertools
2import json
3import linecache
4import os
5import platform
6import sys
7from functools import partial
8
9import pydevd_file_utils
10from _pydev_bundle import pydev_log
11from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
12from _pydevd_bundle._debug_adapter.pydevd_schema import (
13    CompletionsResponseBody, EvaluateResponseBody, ExceptionOptions,
14    GotoTargetsResponseBody, ModulesResponseBody, ProcessEventBody,
15    ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody,
16    SetVariableResponseBody, SourceBreakpoint, SourceResponseBody,
17    VariablesResponseBody, SetBreakpointsResponseBody, Response,
18    Capabilities, PydevdAuthorizeRequest, Request, StepInTargetsResponse, StepInTarget,
19    StepInTargetsResponseBody, SetFunctionBreakpointsResponseBody)
20from _pydevd_bundle.pydevd_api import PyDevdAPI
21from _pydevd_bundle.pydevd_breakpoints import get_exception_class, FunctionBreakpoint
22from _pydevd_bundle.pydevd_comm_constants import (
23    CMD_PROCESS_EVENT, CMD_RETURN, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO,
24    CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, file_system_encoding,
25    CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN)
26from _pydevd_bundle.pydevd_filtering import ExcludeFilter
27from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options, DebugOptions
28from _pydevd_bundle.pydevd_net_command import NetCommand
29from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression, ScopeRequest
30from _pydevd_bundle.pydevd_constants import (PY_IMPL_NAME, DebugInfoHolder, PY_VERSION_STR,
31    PY_IMPL_VERSION_STR, IS_64BIT_PROCESS, IS_PY2)
32from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON
33from _pydevd_frame_eval.pydevd_frame_eval_main import USING_FRAME_EVAL
34from _pydevd_bundle.pydevd_comm import internal_get_step_in_targets_json
35from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
36from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id
37from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
38
39
40def _convert_rules_to_exclude_filters(rules, on_error):
41    exclude_filters = []
42    if not isinstance(rules, list):
43        on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,))
44
45    else:
46        directory_exclude_filters = []
47        module_exclude_filters = []
48        glob_exclude_filters = []
49
50        for rule in rules:
51            if not isinstance(rule, dict):
52                on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,))
53                continue
54
55            include = rule.get('include')
56            if include is None:
57                on_error('Invalid "rule" (expected dict with "include"). Found: %s' % (rule,))
58                continue
59
60            path = rule.get('path')
61            module = rule.get('module')
62            if path is None and module is None:
63                on_error('Invalid "rule" (expected dict with "path" or "module"). Found: %s' % (rule,))
64                continue
65
66            if path is not None:
67                glob_pattern = path
68                if '*' not in path and '?' not in path:
69                    if os.path.isdir(glob_pattern):
70                        # If a directory was specified, add a '/**'
71                        # to be consistent with the glob pattern required
72                        # by pydevd.
73                        if not glob_pattern.endswith('/') and not glob_pattern.endswith('\\'):
74                            glob_pattern += '/'
75                        glob_pattern += '**'
76                    directory_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True))
77                else:
78                    glob_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True))
79
80            elif module is not None:
81                module_exclude_filters.append(ExcludeFilter(module, not include, False))
82
83            else:
84                on_error('Internal error: expected path or module to be specified.')
85
86        # Note that we have to sort the directory/module exclude filters so that the biggest
87        # paths match first.
88        # i.e.: if we have:
89        # /sub1/sub2/sub3
90        # a rule with /sub1/sub2 would match before a rule only with /sub1.
91        directory_exclude_filters = sorted(directory_exclude_filters, key=lambda exclude_filter:-len(exclude_filter.name))
92        module_exclude_filters = sorted(module_exclude_filters, key=lambda exclude_filter:-len(exclude_filter.name))
93        exclude_filters = directory_exclude_filters + glob_exclude_filters + module_exclude_filters
94
95    return exclude_filters
96
97
98class IDMap(object):
99
100    def __init__(self):
101        self._value_to_key = {}
102        self._key_to_value = {}
103        self._next_id = partial(next, itertools.count(0))
104
105    def obtain_value(self, key):
106        return self._key_to_value[key]
107
108    def obtain_key(self, value):
109        try:
110            key = self._value_to_key[value]
111        except KeyError:
112            key = self._next_id()
113            self._key_to_value[key] = value
114            self._value_to_key[value] = key
115        return key
116
117
118class PyDevJsonCommandProcessor(object):
119
120    def __init__(self, from_json):
121        self.from_json = from_json
122        self.api = PyDevdAPI()
123        self._options = DebugOptions()
124        self._next_breakpoint_id = partial(next, itertools.count(0))
125        self._goto_targets_map = IDMap()
126        self._launch_or_attach_request_done = False
127
128    def process_net_command_json(self, py_db, json_contents, send_response=True):
129        '''
130        Processes a debug adapter protocol json command.
131        '''
132
133        DEBUG = False
134
135        try:
136            if isinstance(json_contents, bytes):
137                json_contents = json_contents.decode('utf-8')
138
139            request = self.from_json(json_contents, update_ids_from_dap=True)
140        except Exception as e:
141            try:
142                loaded_json = json.loads(json_contents)
143                request = Request(loaded_json.get('command', '<unknown>'), loaded_json['seq'])
144            except:
145                # There's not much we can do in this case...
146                pydev_log.exception('Error loading json: %s', json_contents)
147                return
148
149            error_msg = str(e)
150            if error_msg.startswith("'") and error_msg.endswith("'"):
151                error_msg = error_msg[1:-1]
152
153            # This means a failure processing the request (but we were able to load the seq,
154            # so, answer with a failure response).
155            def on_request(py_db, request):
156                error_response = {
157                    'type': 'response',
158                    'request_seq': request.seq,
159                    'success': False,
160                    'command': request.command,
161                    'message': error_msg,
162                }
163                return NetCommand(CMD_RETURN, 0, error_response, is_json=True)
164
165        else:
166            if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
167                pydev_log.info('Process %s: %s\n' % (
168                    request.__class__.__name__, json.dumps(request.to_dict(update_ids_to_dap=True), indent=4, sort_keys=True),))
169
170            assert request.type == 'request'
171            method_name = 'on_%s_request' % (request.command.lower(),)
172            on_request = getattr(self, method_name, None)
173            if on_request is None:
174                print('Unhandled: %s not available in PyDevJsonCommandProcessor.\n' % (method_name,))
175                return
176
177            if DEBUG:
178                print('Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name,))
179
180        with py_db._main_lock:
181            if request.__class__ == PydevdAuthorizeRequest:
182                authorize_request = request  # : :type authorize_request: PydevdAuthorizeRequest
183                access_token = authorize_request.arguments.debugServerAccessToken
184                py_db.authentication.login(access_token)
185
186            if not py_db.authentication.is_authenticated():
187                response = Response(
188                    request.seq, success=False, command=request.command, message='Client not authenticated.', body={})
189                cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
190                py_db.writer.add_command(cmd)
191                return
192
193            cmd = on_request(py_db, request)
194            if cmd is not None and send_response:
195                py_db.writer.add_command(cmd)
196
197    def on_pydevdauthorize_request(self, py_db, request):
198        client_access_token = py_db.authentication.client_access_token
199        body = {'clientAccessToken': None}
200        if client_access_token:
201            body['clientAccessToken'] = client_access_token
202
203        response = pydevd_base_schema.build_response(request, kwargs={'body': body})
204        return NetCommand(CMD_RETURN, 0, response, is_json=True)
205
206    def on_initialize_request(self, py_db, request):
207        body = Capabilities(
208            # Supported.
209            supportsConfigurationDoneRequest=True,
210            supportsConditionalBreakpoints=True,
211            supportsHitConditionalBreakpoints=True,
212            supportsEvaluateForHovers=True,
213            supportsSetVariable=True,
214            supportsGotoTargetsRequest=True,
215            supportsCompletionsRequest=True,
216            supportsModulesRequest=True,
217            supportsExceptionOptions=True,
218            supportsValueFormattingOptions=True,
219            supportsExceptionInfoRequest=True,
220            supportTerminateDebuggee=True,
221            supportsDelayedStackTraceLoading=True,
222            supportsLogPoints=True,
223            supportsSetExpression=True,
224            supportsTerminateRequest=True,
225            supportsClipboardContext=True,
226            supportsFunctionBreakpoints=True,
227
228            exceptionBreakpointFilters=[
229                {'filter': 'raised', 'label': 'Raised Exceptions', 'default': False},
230                {'filter': 'uncaught', 'label': 'Uncaught Exceptions', 'default': True},
231                {"filter": "userUnhandled", "label": "User Uncaught Exceptions", "default": False},
232            ],
233
234            # Not supported.
235            supportsStepBack=False,
236            supportsRestartFrame=False,
237            supportsStepInTargetsRequest=True,
238            supportsRestartRequest=False,
239            supportsLoadedSourcesRequest=False,
240            supportsTerminateThreadsRequest=False,
241            supportsDataBreakpoints=False,
242            supportsReadMemoryRequest=False,
243            supportsDisassembleRequest=False,
244            additionalModuleColumns=[],
245            completionTriggerCharacters=[],
246            supportedChecksumAlgorithms=[],
247        ).to_dict()
248
249        # Non-standard capabilities/info below.
250        body['supportsDebuggerProperties'] = True
251
252        body['pydevd'] = pydevd_info = {}
253        pydevd_info['processId'] = os.getpid()
254        self.api.notify_initialize(py_db)
255        response = pydevd_base_schema.build_response(request, kwargs={'body': body})
256        return NetCommand(CMD_RETURN, 0, response, is_json=True)
257
258    def on_configurationdone_request(self, py_db, request):
259        '''
260        :param ConfigurationDoneRequest request:
261        '''
262        if not self._launch_or_attach_request_done:
263            pydev_log.critical('Missing launch request or attach request before configuration done request.')
264
265        self.api.run(py_db)
266        self.api.notify_configuration_done(py_db)
267
268        configuration_done_response = pydevd_base_schema.build_response(request)
269        return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)
270
271    def on_threads_request(self, py_db, request):
272        '''
273        :param ThreadsRequest request:
274        '''
275        return self.api.list_threads(py_db, request.seq)
276
277    def on_terminate_request(self, py_db, request):
278        '''
279        :param TerminateRequest request:
280        '''
281        self._request_terminate_process(py_db)
282        response = pydevd_base_schema.build_response(request)
283        return NetCommand(CMD_RETURN, 0, response, is_json=True)
284
285    def _request_terminate_process(self, py_db):
286        self.api.request_terminate_process(py_db)
287
288    def on_completions_request(self, py_db, request):
289        '''
290        :param CompletionsRequest request:
291        '''
292        arguments = request.arguments  # : :type arguments: CompletionsArguments
293        seq = request.seq
294        text = arguments.text
295        frame_id = arguments.frameId
296        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
297            frame_id)
298
299        if thread_id is None:
300            body = CompletionsResponseBody([])
301            variables_response = pydevd_base_schema.build_response(
302                request,
303                kwargs={
304                    'body': body,
305                    'success': False,
306                    'message': 'Thread to get completions seems to have resumed already.'
307                })
308            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
309
310        # Note: line and column are 1-based (convert to 0-based for pydevd).
311        column = arguments.column - 1
312
313        if arguments.line is None:
314            # line is optional
315            line = -1
316        else:
317            line = arguments.line - 1
318
319        self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column)
320
321    def _resolve_remote_root(self, local_root, remote_root):
322        if remote_root == '.':
323            cwd = os.getcwd()
324            append_pathsep = local_root.endswith('\\') or local_root.endswith('/')
325            return cwd + (os.path.sep if append_pathsep else '')
326        return remote_root
327
328    def _set_debug_options(self, py_db, args, start_reason):
329        rules = args.get('rules')
330        stepping_resumes_all_threads = args.get('steppingResumesAllThreads', True)
331        self.api.set_stepping_resumes_all_threads(py_db, stepping_resumes_all_threads)
332
333        terminate_child_processes = args.get('terminateChildProcesses', True)
334        self.api.set_terminate_child_processes(py_db, terminate_child_processes)
335
336        variable_presentation = args.get('variablePresentation', None)
337        if isinstance(variable_presentation, dict):
338
339            def get_variable_presentation(setting, default):
340                value = variable_presentation.get(setting, default)
341                if value not in ('group', 'inline', 'hide'):
342                    pydev_log.info(
343                        'The value set for "%s" (%s) in the variablePresentation is not valid. Valid values are: "group", "inline", "hide"' % (
344                            setting, value,))
345                    value = default
346
347                return value
348
349            default = get_variable_presentation('all', 'group')
350
351            special_presentation = get_variable_presentation('special', default)
352            function_presentation = get_variable_presentation('function', default)
353            class_presentation = get_variable_presentation('class', default)
354            protected_presentation = get_variable_presentation('protected', default)
355
356            self.api.set_variable_presentation(py_db, self.api.VariablePresentation(
357                special_presentation,
358                function_presentation,
359                class_presentation,
360                protected_presentation
361            ))
362
363        exclude_filters = []
364
365        if rules is not None:
366            exclude_filters = _convert_rules_to_exclude_filters(
367                rules, lambda msg: self.api.send_error_message(py_db, msg))
368
369        self.api.set_exclude_filters(py_db, exclude_filters)
370
371        debug_options = _extract_debug_options(
372            args.get('options'),
373            args.get('debugOptions'),
374        )
375        self._options.update_fom_debug_options(debug_options)
376        self._options.update_from_args(args)
377
378        self.api.set_use_libraries_filter(py_db, self._options.just_my_code)
379
380        path_mappings = []
381        for pathMapping in args.get('pathMappings', []):
382            localRoot = pathMapping.get('localRoot', '')
383            remoteRoot = pathMapping.get('remoteRoot', '')
384            remoteRoot = self._resolve_remote_root(localRoot, remoteRoot)
385            if (localRoot != '') and (remoteRoot != ''):
386                path_mappings.append((localRoot, remoteRoot))
387
388        if bool(path_mappings):
389            pydevd_file_utils.setup_client_server_paths(path_mappings)
390
391        if self._options.redirect_output:
392            py_db.enable_output_redirection(True, True)
393        else:
394            py_db.enable_output_redirection(False, False)
395
396        self.api.set_show_return_values(py_db, self._options.show_return_value)
397
398        if not self._options.break_system_exit_zero:
399            ignore_system_exit_codes = [0, None]
400            if self._options.django_debug or self._options.flask_debug:
401                ignore_system_exit_codes += [3]
402
403            self.api.set_ignore_system_exit_codes(py_db, ignore_system_exit_codes)
404
405        auto_reload = args.get('autoReload', {})
406        if not isinstance(auto_reload, dict):
407            pydev_log.info('Expected autoReload to be a dict. Received: %s' % (auto_reload,))
408            auto_reload = {}
409
410        enable_auto_reload = auto_reload.get('enable', False)
411        watch_dirs = auto_reload.get('watchDirectories')
412        if not watch_dirs:
413            watch_dirs = []
414            # Note: by default this is no longer done because on some cases there are entries in the PYTHONPATH
415            # such as the home directory or /python/x64, where the site packages are in /python/x64/libs, so,
416            # we only watch the current working directory as well as executed script.
417            # check = getattr(sys, 'path', [])[:]
418            # # By default only watch directories that are in the project roots /
419            # # program dir (if available), sys.argv[0], as well as the current dir (we don't want to
420            # # listen to the whole site-packages by default as it can be huge).
421            # watch_dirs = [pydevd_file_utils.absolute_path(w) for w in check]
422            # watch_dirs = [w for w in watch_dirs if py_db.in_project_roots_filename_uncached(w) and os.path.isdir(w)]
423
424            program = args.get('program')
425            if program:
426                if os.path.isdir(program):
427                    watch_dirs.append(program)
428                else:
429                    watch_dirs.append(os.path.dirname(program))
430            watch_dirs.append(os.path.abspath('.'))
431
432            argv = getattr(sys, 'argv', [])
433            if argv:
434                f = argv[0]
435                if os.path.isdir(f):
436                    watch_dirs.append(program)
437                else:
438                    watch_dirs.append(os.path.dirname(f))
439
440        if not isinstance(watch_dirs, (list, set, tuple)):
441            watch_dirs = (watch_dirs,)
442        new_watch_dirs = set()
443        for w in watch_dirs:
444            try:
445                if IS_PY2 and isinstance(w, unicode):
446                    w = w.encode(getfilesystemencoding())
447
448                new_watch_dirs.add(pydevd_file_utils.get_path_with_real_case(pydevd_file_utils.absolute_path(w)))
449            except Exception:
450                pydev_log.exception('Error adding watch dir: %s', w)
451        watch_dirs = new_watch_dirs
452
453        poll_target_time = auto_reload.get('pollingInterval', 1)
454        exclude_patterns = auto_reload.get('exclude', ('**/.git/**', '**/__pycache__/**', '**/node_modules/**', '**/.metadata/**', '**/site-packages/**'))
455        include_patterns = auto_reload.get('include', ('**/*.py', '**/*.pyw'))
456        self.api.setup_auto_reload_watcher(
457            py_db, enable_auto_reload, watch_dirs, poll_target_time, exclude_patterns, include_patterns)
458
459        if self._options.stop_on_entry and start_reason == 'launch':
460            self.api.stop_on_entry()
461
462    def _send_process_event(self, py_db, start_method):
463        argv = getattr(sys, 'argv', [])
464        if len(argv) > 0:
465            name = argv[0]
466        else:
467            name = ''
468
469        if isinstance(name, bytes):
470            name = name.decode(file_system_encoding, 'replace')
471            name = name.encode('utf-8')
472
473        body = ProcessEventBody(
474            name=name,
475            systemProcessId=os.getpid(),
476            isLocalProcess=True,
477            startMethod=start_method,
478        )
479        event = ProcessEvent(body)
480        py_db.writer.add_command(NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True))
481
482    def _handle_launch_or_attach_request(self, py_db, request, start_reason):
483        self._send_process_event(py_db, start_reason)
484        self._launch_or_attach_request_done = True
485        self.api.set_enable_thread_notifications(py_db, True)
486        self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason)
487        response = pydevd_base_schema.build_response(request)
488        return NetCommand(CMD_RETURN, 0, response, is_json=True)
489
490    def on_launch_request(self, py_db, request):
491        '''
492        :param LaunchRequest request:
493        '''
494        return self._handle_launch_or_attach_request(py_db, request, start_reason='launch')
495
496    def on_attach_request(self, py_db, request):
497        '''
498        :param AttachRequest request:
499        '''
500        return self._handle_launch_or_attach_request(py_db, request, start_reason='attach')
501
502    def on_pause_request(self, py_db, request):
503        '''
504        :param PauseRequest request:
505        '''
506        arguments = request.arguments  # : :type arguments: PauseArguments
507        thread_id = arguments.threadId
508
509        self.api.request_suspend_thread(py_db, thread_id=thread_id)
510
511        response = pydevd_base_schema.build_response(request)
512        return NetCommand(CMD_RETURN, 0, response, is_json=True)
513
514    def on_continue_request(self, py_db, request):
515        '''
516        :param ContinueRequest request:
517        '''
518        arguments = request.arguments  # : :type arguments: ContinueArguments
519        thread_id = arguments.threadId
520
521        def on_resumed():
522            body = {'allThreadsContinued': thread_id == '*'}
523            response = pydevd_base_schema.build_response(request, kwargs={'body': body})
524            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
525            py_db.writer.add_command(cmd)
526
527        # Only send resumed notification when it has actually resumed!
528        # (otherwise the user could send a continue, receive the notification and then
529        # request a new pause which would be paused without sending any notification as
530        # it didn't really run in the first place).
531        py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed)
532        self.api.request_resume_thread(thread_id)
533
534    def on_next_request(self, py_db, request):
535        '''
536        :param NextRequest request:
537        '''
538        arguments = request.arguments  # : :type arguments: NextArguments
539        thread_id = arguments.threadId
540
541        if py_db.get_use_libraries_filter():
542            step_cmd_id = CMD_STEP_OVER_MY_CODE
543        else:
544            step_cmd_id = CMD_STEP_OVER
545
546        self.api.request_step(py_db, thread_id, step_cmd_id)
547
548        response = pydevd_base_schema.build_response(request)
549        return NetCommand(CMD_RETURN, 0, response, is_json=True)
550
551    def on_stepin_request(self, py_db, request):
552        '''
553        :param StepInRequest request:
554        '''
555        arguments = request.arguments  # : :type arguments: StepInArguments
556        thread_id = arguments.threadId
557
558        target_id = arguments.targetId
559        if target_id is not None:
560            thread = pydevd_find_thread_by_id(thread_id)
561            info = set_additional_thread_info(thread)
562            target_id_to_smart_step_into_variant = info.target_id_to_smart_step_into_variant
563            if not target_id_to_smart_step_into_variant:
564                variables_response = pydevd_base_schema.build_response(
565                    request,
566                    kwargs={
567                        'success': False,
568                        'message': 'Unable to step into target (no targets are saved in the thread info).'
569                    })
570                return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
571
572            variant = target_id_to_smart_step_into_variant.get(target_id)
573            if variant is not None:
574                parent = variant.parent
575                if parent is not None:
576                    self.api.request_smart_step_into(py_db, request.seq, thread_id, parent.offset, variant.offset)
577                else:
578                    self.api.request_smart_step_into(py_db, request.seq, thread_id, variant.offset, -1)
579            else:
580                variables_response = pydevd_base_schema.build_response(
581                    request,
582                    kwargs={
583                        'success': False,
584                        'message': 'Unable to find step into target %s. Available targets: %s' % (
585                            target_id, target_id_to_smart_step_into_variant)
586                    })
587                return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
588
589        else:
590            if py_db.get_use_libraries_filter():
591                step_cmd_id = CMD_STEP_INTO_MY_CODE
592            else:
593                step_cmd_id = CMD_STEP_INTO
594
595            self.api.request_step(py_db, thread_id, step_cmd_id)
596
597        response = pydevd_base_schema.build_response(request)
598        return NetCommand(CMD_RETURN, 0, response, is_json=True)
599
600    def on_stepintargets_request(self, py_db, request):
601        '''
602        :param StepInTargetsRequest request:
603        '''
604        frame_id = request.arguments.frameId
605        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
606            frame_id)
607
608        if thread_id is None:
609            body = StepInTargetsResponseBody([])
610            variables_response = pydevd_base_schema.build_response(
611                request,
612                kwargs={
613                    'body': body,
614                    'success': False,
615                    'message': 'Unable to get thread_id from frame_id (thread to get step in targets seems to have resumed already).'
616                })
617            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
618
619        py_db.post_method_as_internal_command(
620            thread_id, internal_get_step_in_targets_json, request.seq, thread_id, frame_id, request, set_additional_thread_info)
621
622    def on_stepout_request(self, py_db, request):
623        '''
624        :param StepOutRequest request:
625        '''
626        arguments = request.arguments  # : :type arguments: StepOutArguments
627        thread_id = arguments.threadId
628
629        if py_db.get_use_libraries_filter():
630            step_cmd_id = CMD_STEP_RETURN_MY_CODE
631        else:
632            step_cmd_id = CMD_STEP_RETURN
633
634        self.api.request_step(py_db, thread_id, step_cmd_id)
635
636        response = pydevd_base_schema.build_response(request)
637        return NetCommand(CMD_RETURN, 0, response, is_json=True)
638
639    def _get_hit_condition_expression(self, hit_condition):
640        '''Following hit condition values are supported
641
642        * x or == x when breakpoint is hit x times
643        * >= x when breakpoint is hit more than or equal to x times
644        * % x when breakpoint is hit multiple of x times
645
646        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
647        '''
648        if not hit_condition:
649            return None
650
651        expr = hit_condition.strip()
652        try:
653            int(expr)
654            return '@HIT@ == {}'.format(expr)
655        except ValueError:
656            pass
657
658        if expr.startswith('%'):
659            return '@HIT@ {} == 0'.format(expr)
660
661        if expr.startswith('==') or \
662            expr.startswith('>') or \
663            expr.startswith('<'):
664            return '@HIT@ {}'.format(expr)
665
666        return hit_condition
667
668    def on_disconnect_request(self, py_db, request):
669        '''
670        :param DisconnectRequest request:
671        '''
672        if request.arguments.terminateDebuggee:
673            self._request_terminate_process(py_db)
674            response = pydevd_base_schema.build_response(request)
675            return NetCommand(CMD_RETURN, 0, response, is_json=True)
676
677        self._launch_or_attach_request_done = False
678        py_db.enable_output_redirection(False, False)
679        self.api.request_disconnect(py_db, resume_threads=True)
680
681        response = pydevd_base_schema.build_response(request)
682        return NetCommand(CMD_RETURN, 0, response, is_json=True)
683
684    def _verify_launch_or_attach_done(self, request):
685        if not self._launch_or_attach_request_done:
686            # Note that to validate the breakpoints we need the launch request to be done already
687            # (otherwise the filters wouldn't be set for the breakpoint validation).
688            if request.command == 'setFunctionBreakpoints':
689                body = SetFunctionBreakpointsResponseBody([])
690            else:
691                body = SetBreakpointsResponseBody([])
692            response = pydevd_base_schema.build_response(
693                request,
694                kwargs={
695                    'body': body,
696                    'success': False,
697                    'message': 'Breakpoints may only be set after the launch request is received.'
698                })
699            return NetCommand(CMD_RETURN, 0, response, is_json=True)
700
701    def on_setfunctionbreakpoints_request(self, py_db, request):
702        '''
703        :param SetFunctionBreakpointsRequest request:
704        '''
705        response = self._verify_launch_or_attach_done(request)
706        if response is not None:
707            return response
708
709        arguments = request.arguments  # : :type arguments: SetFunctionBreakpointsArguments
710        function_breakpoints = []
711        suspend_policy = 'ALL'
712
713        # Not currently covered by the DAP.
714        is_logpoint = False
715        expression = None
716
717        breakpoints_set = []
718        for bp in arguments.breakpoints:
719            hit_condition = self._get_hit_condition_expression(bp.get('hitCondition'))
720            condition = bp.get('condition')
721
722            function_breakpoints.append(
723                FunctionBreakpoint(bp['name'], condition, expression, suspend_policy, hit_condition, is_logpoint))
724
725            # Note: always succeeds.
726            breakpoints_set.append(pydevd_schema.Breakpoint(
727                    verified=True, id=self._next_breakpoint_id()).to_dict())
728
729        self.api.set_function_breakpoints(py_db, function_breakpoints)
730
731        body = {'breakpoints': breakpoints_set}
732        set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
733        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)
734
735    def on_setbreakpoints_request(self, py_db, request):
736        '''
737        :param SetBreakpointsRequest request:
738        '''
739        response = self._verify_launch_or_attach_done(request)
740        if response is not None:
741            return response
742
743        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
744        # TODO: Path is optional here it could be source reference.
745        filename = self.api.filename_to_str(arguments.source.path)
746        func_name = 'None'
747
748        self.api.remove_all_breakpoints(py_db, filename)
749
750        btype = 'python-line'
751        suspend_policy = 'ALL'
752
753        if not filename.lower().endswith('.py'):  # Note: check based on original file, not mapping.
754            if self._options.django_debug:
755                btype = 'django-line'
756            elif self._options.flask_debug:
757                btype = 'jinja2-line'
758
759        breakpoints_set = []
760
761        for source_breakpoint in arguments.breakpoints:
762            source_breakpoint = SourceBreakpoint(**source_breakpoint)
763            line = source_breakpoint.line
764            condition = source_breakpoint.condition
765            breakpoint_id = line
766
767            hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition)
768            log_message = source_breakpoint.logMessage
769            if not log_message:
770                is_logpoint = None
771                expression = None
772            else:
773                is_logpoint = True
774                expression = convert_dap_log_message_to_expression(log_message)
775
776            result = self.api.add_breakpoint(
777                py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint, adjust_line=True)
778            error_code = result.error_code
779
780            if error_code:
781                if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
782                    error_msg = 'Breakpoint in file that does not exist.'
783
784                elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
785                    error_msg = 'Breakpoint in file excluded by filters.'
786                    if py_db.get_use_libraries_filter():
787                        error_msg += ('\nNote: may be excluded because of "justMyCode" option (default == true).'
788                                      'Try setting \"justMyCode\": false in the debug configuration (e.g., launch.json).\n')
789
790                else:
791                    # Shouldn't get here.
792                    error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).'
793
794                breakpoints_set.append(pydevd_schema.Breakpoint(
795                    verified=False, line=result.translated_line, message=error_msg, source=arguments.source).to_dict())
796            else:
797                # Note that the id is made up (the id for pydevd is unique only within a file, so, the
798                # line is used for it).
799                # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
800                breakpoints_set.append(pydevd_schema.Breakpoint(
801                    verified=True, id=self._next_breakpoint_id(), line=result.translated_line, source=arguments.source).to_dict())
802
803        body = {'breakpoints': breakpoints_set}
804        set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
805        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)
806
807    def on_setexceptionbreakpoints_request(self, py_db, request):
808        '''
809        :param SetExceptionBreakpointsRequest request:
810        '''
811        # : :type arguments: SetExceptionBreakpointsArguments
812        arguments = request.arguments
813        filters = arguments.filters
814        exception_options = arguments.exceptionOptions
815        self.api.remove_all_exception_breakpoints(py_db)
816
817        # Can't set these in the DAP.
818        condition = None
819        expression = None
820        notify_on_first_raise_only = False
821
822        ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0
823
824        if exception_options:
825            break_raised = False
826            break_uncaught = False
827
828            for option in exception_options:
829                option = ExceptionOptions(**option)
830                if not option.path:
831                    continue
832
833                # never: never breaks
834                #
835                # always: always breaks
836                #
837                # unhandled: breaks when exception unhandled
838                #
839                # userUnhandled: breaks if the exception is not handled by user code
840
841                notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0
842                notify_on_unhandled_exceptions = 1 if option.breakMode == 'unhandled' else 0
843                notify_on_user_unhandled_exceptions = 1 if option.breakMode == 'userUnhandled' else 0
844                exception_paths = option.path
845                break_raised |= notify_on_handled_exceptions
846                break_uncaught |= notify_on_unhandled_exceptions
847
848                exception_names = []
849                if len(exception_paths) == 0:
850                    continue
851
852                elif len(exception_paths) == 1:
853                    if 'Python Exceptions' in exception_paths[0]['names']:
854                        exception_names = ['BaseException']
855
856                else:
857                    path_iterator = iter(exception_paths)
858                    if 'Python Exceptions' in next(path_iterator)['names']:
859                        for path in path_iterator:
860                            for ex_name in path['names']:
861                                exception_names.append(ex_name)
862
863                for exception_name in exception_names:
864                    self.api.add_python_exception_breakpoint(
865                        py_db,
866                        exception_name,
867                        condition,
868                        expression,
869                        notify_on_handled_exceptions,
870                        notify_on_unhandled_exceptions,
871                        notify_on_user_unhandled_exceptions,
872                        notify_on_first_raise_only,
873                        ignore_libraries
874                    )
875
876        else:
877            break_raised = 'raised' in filters
878            break_uncaught = 'uncaught' in filters
879            break_user = 'userUnhandled' in filters
880            if break_raised or break_uncaught or break_user:
881                notify_on_handled_exceptions = 1 if break_raised else 0
882                notify_on_unhandled_exceptions = 1 if break_uncaught else 0
883                notify_on_user_unhandled_exceptions = 1 if break_user else 0
884                exception = 'BaseException'
885
886                self.api.add_python_exception_breakpoint(
887                    py_db,
888                    exception,
889                    condition,
890                    expression,
891                    notify_on_handled_exceptions,
892                    notify_on_unhandled_exceptions,
893                    notify_on_user_unhandled_exceptions,
894                    notify_on_first_raise_only,
895                    ignore_libraries
896                )
897
898        if break_raised:
899            btype = None
900            if self._options.django_debug:
901                btype = 'django'
902            elif self._options.flask_debug:
903                btype = 'jinja2'
904
905            if btype:
906                self.api.add_plugins_exception_breakpoint(
907                    py_db, btype, 'BaseException')  # Note: Exception name could be anything here.
908
909        # Note: no body required on success.
910        set_breakpoints_response = pydevd_base_schema.build_response(request)
911        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)
912
913    def on_stacktrace_request(self, py_db, request):
914        '''
915        :param StackTraceRequest request:
916        '''
917        # : :type stack_trace_arguments: StackTraceArguments
918        stack_trace_arguments = request.arguments
919        thread_id = stack_trace_arguments.threadId
920        start_frame = stack_trace_arguments.startFrame
921        levels = stack_trace_arguments.levels
922
923        fmt = stack_trace_arguments.format
924        if hasattr(fmt, 'to_dict'):
925            fmt = fmt.to_dict()
926        self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels)
927
928    def on_exceptioninfo_request(self, py_db, request):
929        '''
930        :param ExceptionInfoRequest request:
931        '''
932        # : :type exception_into_arguments: ExceptionInfoArguments
933        exception_into_arguments = request.arguments
934        thread_id = exception_into_arguments.threadId
935        max_frames = self._options.max_exception_stack_frames
936        self.api.request_exception_info_json(py_db, request, thread_id, max_frames)
937
938    def on_scopes_request(self, py_db, request):
939        '''
940        Scopes are the top-level items which appear for a frame (so, we receive the frame id
941        and provide the scopes it has).
942
943        :param ScopesRequest request:
944        '''
945        frame_id = request.arguments.frameId
946
947        variables_reference = frame_id
948        scopes = [
949            Scope('Locals', ScopeRequest(int(variables_reference), 'locals'), False, presentationHint='locals'),
950            Scope('Globals', ScopeRequest(int(variables_reference), 'globals'), False),
951        ]
952        body = ScopesResponseBody(scopes)
953        scopes_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
954        return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True)
955
956    def on_evaluate_request(self, py_db, request):
957        '''
958        :param EvaluateRequest request:
959        '''
960        # : :type arguments: EvaluateArguments
961        arguments = request.arguments
962
963        if arguments.frameId is None:
964            self.api.request_exec_or_evaluate_json(py_db, request, thread_id='*')
965        else:
966            thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
967                arguments.frameId)
968
969            if thread_id is not None:
970                self.api.request_exec_or_evaluate_json(
971                    py_db, request, thread_id)
972            else:
973                body = EvaluateResponseBody('', 0)
974                response = pydevd_base_schema.build_response(
975                    request,
976                    kwargs={
977                        'body': body,
978                        'success': False,
979                        'message': 'Unable to find thread for evaluation.'
980                    })
981                return NetCommand(CMD_RETURN, 0, response, is_json=True)
982
983    def on_setexpression_request(self, py_db, request):
984        # : :type arguments: SetExpressionArguments
985        arguments = request.arguments
986
987        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
988            arguments.frameId)
989
990        if thread_id is not None:
991            self.api.request_set_expression_json(py_db, request, thread_id)
992        else:
993            body = SetExpressionResponseBody('')
994            response = pydevd_base_schema.build_response(
995                request,
996                kwargs={
997                    'body': body,
998                    'success': False,
999                    'message': 'Unable to find thread to set expression.'
1000                })
1001            return NetCommand(CMD_RETURN, 0, response, is_json=True)
1002
1003    def on_variables_request(self, py_db, request):
1004        '''
1005        Variables can be asked whenever some place returned a variables reference (so, it
1006        can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).
1007
1008        Note that in the DAP the variables reference requires a unique int... the way this works for
1009        pydevd is that an instance is generated for that specific variable reference and we use its
1010        id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
1011        is added to a dict which is only valid while the thread is suspended and later cleared when
1012        the related thread resumes execution).
1013
1014        see: SuspendedFramesManager
1015
1016        :param VariablesRequest request:
1017        '''
1018        arguments = request.arguments  # : :type arguments: VariablesArguments
1019        variables_reference = arguments.variablesReference
1020
1021        if isinstance(variables_reference, ScopeRequest):
1022            variables_reference = variables_reference.variable_reference
1023
1024        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
1025            variables_reference)
1026        if thread_id is not None:
1027            self.api.request_get_variable_json(py_db, request, thread_id)
1028        else:
1029            variables = []
1030            body = VariablesResponseBody(variables)
1031            variables_response = pydevd_base_schema.build_response(request, kwargs={
1032                'body': body,
1033                'success': False,
1034                'message': 'Unable to find thread to evaluate variable reference.'
1035            })
1036            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
1037
1038    def on_setvariable_request(self, py_db, request):
1039        arguments = request.arguments  # : :type arguments: SetVariableArguments
1040        variables_reference = arguments.variablesReference
1041
1042        if isinstance(variables_reference, ScopeRequest):
1043            variables_reference = variables_reference.variable_reference
1044
1045        if arguments.name.startswith('(return) '):
1046            response = pydevd_base_schema.build_response(
1047                request,
1048                kwargs={
1049                    'body': SetVariableResponseBody(''),
1050                    'success': False,
1051                    'message': 'Cannot change return value'
1052                })
1053            return NetCommand(CMD_RETURN, 0, response, is_json=True)
1054
1055        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
1056            variables_reference)
1057
1058        if thread_id is not None:
1059            self.api.request_change_variable_json(py_db, request, thread_id)
1060        else:
1061            response = pydevd_base_schema.build_response(
1062                request,
1063                kwargs={
1064                    'body': SetVariableResponseBody(''),
1065                    'success': False,
1066                    'message': 'Unable to find thread to evaluate variable reference.'
1067                })
1068            return NetCommand(CMD_RETURN, 0, response, is_json=True)
1069
1070    def on_modules_request(self, py_db, request):
1071        modules_manager = py_db.cmd_factory.modules_manager  # : :type modules_manager: ModulesManager
1072        modules_info = modules_manager.get_modules_info()
1073        body = ModulesResponseBody(modules_info)
1074        variables_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
1075        return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
1076
1077    def on_source_request(self, py_db, request):
1078        '''
1079        :param SourceRequest request:
1080        '''
1081        source_reference = request.arguments.sourceReference
1082        server_filename = None
1083        content = None
1084
1085        if source_reference != 0:
1086            server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference)
1087            if not server_filename:
1088                server_filename = pydevd_file_utils.get_source_reference_filename_from_linecache(source_reference)
1089
1090            if server_filename:
1091                # Try direct file access first - it's much faster when available.
1092                try:
1093                    with open(server_filename, 'r') as stream:
1094                        content = stream.read()
1095                except:
1096                    pass
1097
1098                if content is None:
1099                    # File might not exist at all, or we might not have a permission to read it,
1100                    # but it might also be inside a zipfile, or an IPython cell. In this case,
1101                    # linecache might still be able to retrieve the source.
1102                    lines = (linecache.getline(server_filename, i) for i in itertools.count(1))
1103                    lines = itertools.takewhile(bool, lines)  # empty lines are '\n', EOF is ''
1104
1105                    # If we didn't get at least one line back, reset it to None so that it's
1106                    # reported as error below, and not as an empty file.
1107                    content = ''.join(lines) or None
1108
1109            if content is None:
1110                frame_id = pydevd_file_utils.get_frame_id_from_source_reference(source_reference)
1111                pydev_log.debug('Found frame id: %s for source reference: %s', frame_id, source_reference)
1112                if frame_id is not None:
1113                    try:
1114                        content = self.api.get_decompiled_source_from_frame_id(py_db, frame_id)
1115                    except Exception:
1116                        pydev_log.exception('Error getting source for frame id: %s', frame_id)
1117                        content = None
1118
1119        body = SourceResponseBody(content or '')
1120        response_args = {'body': body}
1121
1122        if content is None:
1123            if source_reference == 0:
1124                message = 'Source unavailable'
1125            elif server_filename:
1126                message = 'Unable to retrieve source for %s' % (server_filename,)
1127            else:
1128                message = 'Invalid sourceReference %d' % (source_reference,)
1129            response_args.update({'success': False, 'message': message})
1130
1131        response = pydevd_base_schema.build_response(request, kwargs=response_args)
1132        return NetCommand(CMD_RETURN, 0, response, is_json=True)
1133
1134    def on_gototargets_request(self, py_db, request):
1135        path = request.arguments.source.path
1136        line = request.arguments.line
1137        target_id = self._goto_targets_map.obtain_key((path, line))
1138        target = {
1139            'id': target_id,
1140            'label': '%s:%s' % (path, line),
1141            'line': line
1142        }
1143        body = GotoTargetsResponseBody(targets=[target])
1144        response_args = {'body': body}
1145        response = pydevd_base_schema.build_response(request, kwargs=response_args)
1146        return NetCommand(CMD_RETURN, 0, response, is_json=True)
1147
1148    def on_goto_request(self, py_db, request):
1149        target_id = int(request.arguments.targetId)
1150        thread_id = request.arguments.threadId
1151        try:
1152            path, line = self._goto_targets_map.obtain_value(target_id)
1153        except KeyError:
1154            response = pydevd_base_schema.build_response(
1155                request,
1156                kwargs={
1157                    'body': {},
1158                    'success': False,
1159                    'message': 'Unknown goto target id: %d' % (target_id,),
1160                })
1161            return NetCommand(CMD_RETURN, 0, response, is_json=True)
1162
1163        self.api.request_set_next(py_db, request.seq, thread_id, CMD_SET_NEXT_STATEMENT, path, line, '*')
1164        # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response
1165        return None
1166
1167    def on_setdebuggerproperty_request(self, py_db, request):
1168        args = request.arguments  # : :type args: SetDebuggerPropertyArguments
1169        if args.ideOS is not None:
1170            self.api.set_ide_os(args.ideOS)
1171
1172        if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None:
1173            start_patterns = tuple(args.dontTraceStartPatterns)
1174            end_patterns = tuple(args.dontTraceEndPatterns)
1175            self.api.set_dont_trace_start_end_patterns(py_db, start_patterns, end_patterns)
1176
1177        if args.skipSuspendOnBreakpointException is not None:
1178            py_db.skip_suspend_on_breakpoint_exception = tuple(
1179                get_exception_class(x) for x in args.skipSuspendOnBreakpointException)
1180
1181        if args.skipPrintBreakpointException is not None:
1182            py_db.skip_print_breakpoint_exception = tuple(
1183                get_exception_class(x) for x in args.skipPrintBreakpointException)
1184
1185        if args.multiThreadsSingleNotification is not None:
1186            py_db.multi_threads_single_notification = args.multiThreadsSingleNotification
1187
1188        # TODO: Support other common settings. Note that not all of these might be relevant to python.
1189        # JustMyCodeStepping: 0 or 1
1190        # AllowOutOfProcessSymbols: 0 or 1
1191        # DisableJITOptimization: 0 or 1
1192        # InterpreterOptions: 0 or 1
1193        # StopOnExceptionCrossingManagedBoundary: 0 or 1
1194        # WarnIfNoUserCodeOnLaunch: 0 or 1
1195        # EnableStepFiltering: true of false
1196
1197        response = pydevd_base_schema.build_response(request, kwargs={'body': {}})
1198        return NetCommand(CMD_RETURN, 0, response, is_json=True)
1199
1200    def on_pydevdsysteminfo_request(self, py_db, request):
1201        try:
1202            pid = os.getpid()
1203        except AttributeError:
1204            pid = None
1205
1206        # It's possible to have the ppid reported from args. In this case, use that instead of the
1207        # real ppid (athough we're using `ppid`, what we want in meaning is the `launcher_pid` --
1208        # so, if a python process is launched from another python process, consider that process the
1209        # parent and not any intermediary stubs).
1210
1211        ppid = py_db.get_arg_ppid() or self.api.get_ppid()
1212
1213        try:
1214            impl_desc = platform.python_implementation()
1215        except AttributeError:
1216            impl_desc = PY_IMPL_NAME
1217
1218        py_info = pydevd_schema.PydevdPythonInfo(
1219            version=PY_VERSION_STR,
1220            implementation=pydevd_schema.PydevdPythonImplementationInfo(
1221                name=PY_IMPL_NAME,
1222                version=PY_IMPL_VERSION_STR,
1223                description=impl_desc,
1224            )
1225        )
1226        platform_info = pydevd_schema.PydevdPlatformInfo(name=sys.platform)
1227        process_info = pydevd_schema.PydevdProcessInfo(
1228            pid=pid,
1229            ppid=ppid,
1230            executable=sys.executable,
1231            bitness=64 if IS_64BIT_PROCESS else 32,
1232        )
1233        pydevd_info = pydevd_schema.PydevdInfo(
1234            usingCython=USING_CYTHON,
1235            usingFrameEval=USING_FRAME_EVAL,
1236        )
1237        body = {
1238            'python': py_info,
1239            'platform': platform_info,
1240            'process': process_info,
1241            'pydevd': pydevd_info,
1242        }
1243        response = pydevd_base_schema.build_response(request, kwargs={'body': body})
1244        return NetCommand(CMD_RETURN, 0, response, is_json=True)
1245
1246    def on_setpydevdsourcemap_request(self, py_db, request):
1247        args = request.arguments  # : :type args: SetPydevdSourceMapArguments
1248        SourceMappingEntry = self.api.SourceMappingEntry
1249
1250        path = args.source.path
1251        source_maps = args.pydevdSourceMaps
1252        # : :type source_map: PydevdSourceMap
1253        new_mappings = [
1254            SourceMappingEntry(
1255                source_map['line'],
1256                source_map['endLine'],
1257                source_map['runtimeLine'],
1258                self.api.filename_to_str(source_map['runtimeSource']['path'])
1259            ) for source_map in source_maps
1260        ]
1261
1262        error_msg = self.api.set_source_mapping(py_db, path, new_mappings)
1263        if error_msg:
1264            response = pydevd_base_schema.build_response(
1265                request,
1266                kwargs={
1267                    'body': {},
1268                    'success': False,
1269                    'message': error_msg,
1270                })
1271            return NetCommand(CMD_RETURN, 0, response, is_json=True)
1272
1273        response = pydevd_base_schema.build_response(request)
1274        return NetCommand(CMD_RETURN, 0, response, is_json=True)
1275
1276