From cbb5a51c4b1c5ebaabec539e0aa2252a86ece7b3 Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Thu, 11 Oct 2018 23:07:20 -0700 Subject: [PATCH 1/4] Fix #896: Changes in CLI arguments between versions Remove --server-host. Server mode is always implied by default now in all circumstances, and --client is used to specify client mode. --- ptvsd/__main__.py | 34 +++------- tests/helpers/debugadapter.py | 11 ++- tests/ptvsd/test___main__.py | 123 +++------------------------------- 3 files changed, 27 insertions(+), 141 deletions(-) diff --git a/ptvsd/__main__.py b/ptvsd/__main__.py index 94b082f7..25894278 100644 --- a/ptvsd/__main__.py +++ b/ptvsd/__main__.py @@ -24,8 +24,6 @@ For the PyDevd CLI handling see: PYDEVD_OPTS = { '--file', - '--client', - #'--port', '--vm_type', } @@ -45,8 +43,8 @@ PYDEVD_FLAGS = { } USAGE = """ - {0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT -m MODULE [arg ...] - {0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT FILENAME [arg ...] + {0} [-h] [-V] [--nodebug] [--client] [--host HOST] --port PORT -m MODULE [arg ...] + {0} [-h] [-V] [--nodebug] [--client] [--host HOST] --port PORT FILENAME [arg ...] {0} [-h] [-V] --host HOST --port PORT --pid PROCESS_ID """ # noqa @@ -106,9 +104,7 @@ def _group_args(argv): if gottarget: script = argv[i:] + script break - if arg == '--client': - arg = '--host' - elif arg == '--file': + if arg == '--file': if nextarg is None: # The filename is missing... pydevd.append(arg) continue # This will get handled later. @@ -131,14 +127,14 @@ def _group_args(argv): supported.append(arg) # ptvsd support - elif arg in ('--host', '--server-host', '--port', '--pid', '-m'): + elif arg in ('--host', '--port', '--pid', '-m'): if arg == '-m' or arg == '--pid': gottarget = True supported.append(arg) if nextarg is not None: supported.append(nextarg) skip += 1 - elif arg in ('--single-session', '--wait'): + elif arg in ('--single-session', '--wait', '--client'): supported.append(arg) elif not arg.startswith('-'): supported.append(arg) @@ -159,10 +155,9 @@ def _parse_args(prog, argv): ) parser.add_argument('--nodebug', action='store_true') + parser.add_argument('--client', action='store_true') - host = parser.add_mutually_exclusive_group() - host.add_argument('--host') - host.add_argument('--server-host') + parser.add_argument('--host') parser.add_argument('--port', type=int, required=True) target = parser.add_mutually_exclusive_group(required=True) @@ -179,17 +174,10 @@ def _parse_args(prog, argv): args = parser.parse_args(argv) ns = vars(args) - serverhost = ns.pop('server_host', None) - clienthost = ns.pop('host', None) - if serverhost: - args.address = Address.as_server(serverhost, ns.pop('port')) - elif not clienthost: - if args.nodebug: - args.address = Address.as_client(clienthost, ns.pop('port')) - else: - args.address = Address.as_server(clienthost, ns.pop('port')) - else: - args.address = Address.as_client(clienthost, ns.pop('port')) + host = ns.pop('host', None) + port = ns.pop('port') + client = ns.pop('client') + args.address = (Address.as_client if client else Address.as_server)(host, port) # noqa pid = ns.pop('pid') module = ns.pop('module') diff --git a/tests/helpers/debugadapter.py b/tests/helpers/debugadapter.py index bb69efe8..ab7a73f2 100644 --- a/tests/helpers/debugadapter.py +++ b/tests/helpers/debugadapter.py @@ -235,20 +235,19 @@ class DebugAdapter(Closeable): def _ensure_addr(cls, argv, addr): if '--host' in argv: raise ValueError("unexpected '--host' in argv") - if '--server-host' in argv: - raise ValueError("unexpected '--server-host' in argv") if '--port' in argv: raise ValueError("unexpected '--port' in argv") + if '--client' in argv: + raise ValueError("unexpected '--client' in argv") host, port = addr argv.insert(0, str(port)) argv.insert(0, '--port') argv.insert(0, host) - if addr.isserver: - argv.insert(0, '--server-host') - else: - argv.insert(0, '--host') + argv.insert(0, '--host') + if not addr.isserver: + argv.insert(0, '--client') def __init__(self, proc, addr, owned=False): super(DebugAdapter, self).__init__() diff --git a/tests/ptvsd/test___main__.py b/tests/ptvsd/test___main__.py index f921d8f6..ec4c72e7 100644 --- a/tests/ptvsd/test___main__.py +++ b/tests/ptvsd/test___main__.py @@ -29,7 +29,7 @@ class ParseArgsTests(unittest.TestCase): def test_module_server(self): args, extra = parse_args([ 'eggs', - '--server-host', '10.0.1.1', + '--host', '10.0.1.1', '--port', '8888', '-m', 'spam', ]) @@ -48,6 +48,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--nodebug', + '--client', '--port', '8888', '-m', 'spam', ]) @@ -82,7 +83,7 @@ class ParseArgsTests(unittest.TestCase): def test_script_server(self): args, extra = parse_args([ 'eggs', - '--server-host', '10.0.1.1', + '--host', '10.0.1.1', '--port', '8888', 'spam.py', ]) @@ -101,6 +102,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--nodebug', + '--client', '--port', '8888', 'spam.py', ]) @@ -118,6 +120,7 @@ class ParseArgsTests(unittest.TestCase): def test_remote(self): args, extra = parse_args([ 'eggs', + '--client', '--host', '1.2.3.4', '--port', '8888', 'spam.py', @@ -136,6 +139,7 @@ class ParseArgsTests(unittest.TestCase): def test_remote_localhost(self): args, extra = parse_args([ 'eggs', + '--client', '--host', 'localhost', '--port', '8888', 'spam.py', @@ -155,6 +159,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--nodebug', + '--client', '--host', '1.2.3.4', '--port', '8888', 'spam.py', @@ -192,7 +197,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--single-session', - '--server-host', '1.2.3.4', + '--host', '1.2.3.4', '--port', '8888', 'spam.py', ]) @@ -210,6 +215,7 @@ class ParseArgsTests(unittest.TestCase): def test_remote_wait(self): args, extra = parse_args([ 'eggs', + '--client', '--host', '1.2.3.4', '--port', '8888', '--wait', @@ -267,6 +273,7 @@ class ParseArgsTests(unittest.TestCase): 'eggs', '--DEBUG', '--nodebug', + '--client', '--port', '8888', '--vm_type', '???', 'spam.py', @@ -327,115 +334,6 @@ class ParseArgsTests(unittest.TestCase): 'spam.py', ]) - def test_backward_compatibility_host(self): - args, extra = parse_args([ - 'eggs', - '--client', '1.2.3.4', - '--port', '8888', - '-m', 'spam', - ]) - - self.assertEqual(vars(args), { - 'kind': 'module', - 'name': 'spam', - 'address': Address.as_client('1.2.3.4', 8888), - 'nodebug': False, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - - def test_backward_compatibility_host_nodebug(self): - args, extra = parse_args([ - 'eggs', - '--nodebug', - '--client', '1.2.3.4', - '--port', '8888', - '-m', 'spam', - ]) - - self.assertEqual(vars(args), { - 'kind': 'module', - 'name': 'spam', - 'address': Address.as_client('1.2.3.4', 8888), - 'nodebug': True, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - - def test_backward_compatibility_module(self): - args, extra = parse_args([ - 'eggs', - '--port', '8888', - '--module', - '--file', 'spam:', - ]) - - self.assertEqual(vars(args), { - 'kind': 'module', - 'name': 'spam', - 'address': Address.as_server(None, 8888), - 'nodebug': False, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - - def test_backward_compatibility_module_nodebug(self): - args, extra = parse_args([ - 'eggs', - '--nodebug', - '--port', '8888', - '--module', - '--file', 'spam:', - ]) - - self.assertEqual(vars(args), { - 'kind': 'module', - 'name': 'spam', - 'address': Address.as_client(None, 8888), - 'nodebug': True, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - - def test_backward_compatibility_script(self): - args, extra = parse_args([ - 'eggs', - '--port', '8888', - '--file', 'spam.py', - ]) - - self.assertEqual(vars(args), { - 'kind': 'script', - 'name': 'spam.py', - 'address': Address.as_server(None, 8888), - 'nodebug': False, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - - def test_backward_compatibility_script_nodebug(self): - args, extra = parse_args([ - 'eggs', - '--nodebug', - '--port', '8888', - '--file', 'spam.py', - ]) - - self.assertEqual(vars(args), { - 'kind': 'script', - 'name': 'spam.py', - 'address': Address.as_client(None, 8888), - 'nodebug': True, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) - def test_pseudo_backward_compatibility(self): args, extra = parse_args([ 'eggs', @@ -458,6 +356,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--nodebug', + '--client', '--port', '8888', '--module', '--file', 'spam', From bd220d033355ed626647f419c8f63f0ab5fe47c6 Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Tue, 16 Oct 2018 16:03:10 -0700 Subject: [PATCH 2/4] Fix #925: Make --host a required switch --- ptvsd/__main__.py | 2 +- tests/ptvsd/test___main__.py | 45 +++++++++++++-------------- tests/system_tests/test_connection.py | 1 + 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ptvsd/__main__.py b/ptvsd/__main__.py index 25894278..f6dc445a 100644 --- a/ptvsd/__main__.py +++ b/ptvsd/__main__.py @@ -157,7 +157,7 @@ def _parse_args(prog, argv): parser.add_argument('--nodebug', action='store_true') parser.add_argument('--client', action='store_true') - parser.add_argument('--host') + parser.add_argument('--host', required=True) parser.add_argument('--port', type=int, required=True) target = parser.add_mutually_exclusive_group(required=True) diff --git a/tests/ptvsd/test___main__.py b/tests/ptvsd/test___main__.py index ec4c72e7..51a8aa3a 100644 --- a/tests/ptvsd/test___main__.py +++ b/tests/ptvsd/test___main__.py @@ -9,22 +9,13 @@ class ParseArgsTests(unittest.TestCase): EXPECTED_EXTRA = ['--'] - def test_module(self): - args, extra = parse_args([ - 'eggs', - '--port', '8888', - '-m', 'spam', - ]) - - self.assertEqual(vars(args), { - 'kind': 'module', - 'name': 'spam', - 'address': Address.as_server(None, 8888), - 'nodebug': False, - 'single_session': False, - 'wait': False, - }) - self.assertEqual(extra, self.EXPECTED_EXTRA) + def test_host_required(self): + with self.assertRaises(SystemExit): + parse_args([ + 'eggs', + '--port', '8888', + '-m', 'spam', + ]) def test_module_server(self): args, extra = parse_args([ @@ -49,6 +40,7 @@ class ParseArgsTests(unittest.TestCase): 'eggs', '--nodebug', '--client', + '--host', 'localhost', '--port', '8888', '-m', 'spam', ]) @@ -56,7 +48,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'module', 'name': 'spam', - 'address': Address.as_client(None, 8888), + 'address': Address.as_client('localhost', 8888), 'nodebug': True, 'single_session': False, 'wait': False, @@ -66,6 +58,7 @@ class ParseArgsTests(unittest.TestCase): def test_script(self): args, extra = parse_args([ 'eggs', + '--host', 'localhost', '--port', '8888', 'spam.py', ]) @@ -73,7 +66,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam.py', - 'address': Address.as_server(None, 8888), + 'address': Address.as_server('localhost', 8888), 'nodebug': False, 'single_session': False, 'wait': False, @@ -103,6 +96,7 @@ class ParseArgsTests(unittest.TestCase): 'eggs', '--nodebug', '--client', + '--host', 'localhost', '--port', '8888', 'spam.py', ]) @@ -110,7 +104,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam.py', - 'address': Address.as_client(None, 8888), + 'address': Address.as_client('localhost', 8888), 'nodebug': True, 'single_session': False, 'wait': False, @@ -179,6 +173,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--single-session', + '--host', 'localhost', '--port', '8888', 'spam.py', ]) @@ -236,6 +231,7 @@ class ParseArgsTests(unittest.TestCase): args, extra = parse_args([ 'eggs', '--DEBUG', + '--host', 'localhost', '--port', '8888', '--vm_type', '???', 'spam.py', @@ -251,7 +247,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam.py', - 'address': Address.as_server(None, 8888), + 'address': Address.as_server('localhost', 8888), 'nodebug': False, 'single_session': False, 'wait': False, @@ -274,6 +270,7 @@ class ParseArgsTests(unittest.TestCase): '--DEBUG', '--nodebug', '--client', + '--host', 'localhost', '--port', '8888', '--vm_type', '???', 'spam.py', @@ -289,7 +286,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam.py', - 'address': Address.as_client(None, 8888), + 'address': Address.as_client('localhost', 8888), 'nodebug': True, 'single_session': False, 'wait': False, @@ -337,6 +334,7 @@ class ParseArgsTests(unittest.TestCase): def test_pseudo_backward_compatibility(self): args, extra = parse_args([ 'eggs', + '--host', 'localhost', '--port', '8888', '--module', '--file', 'spam', @@ -345,7 +343,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam', - 'address': Address.as_server(None, 8888), + 'address': Address.as_server('localhost', 8888), 'nodebug': False, 'single_session': False, 'wait': False, @@ -357,6 +355,7 @@ class ParseArgsTests(unittest.TestCase): 'eggs', '--nodebug', '--client', + '--host', 'localhost', '--port', '8888', '--module', '--file', 'spam', @@ -365,7 +364,7 @@ class ParseArgsTests(unittest.TestCase): self.assertEqual(vars(args), { 'kind': 'script', 'name': 'spam', - 'address': Address.as_client(None, 8888), + 'address': Address.as_client('localhost', 8888), 'nodebug': True, 'single_session': False, 'wait': False, diff --git a/tests/system_tests/test_connection.py b/tests/system_tests/test_connection.py index 73096494..b65a31ad 100644 --- a/tests/system_tests/test_connection.py +++ b/tests/system_tests/test_connection.py @@ -115,6 +115,7 @@ class RawConnectionTests(unittest.TestCase): proc = Proc.start_python_module('ptvsd', [ '--server', '--wait', + '--host', 'localhost', '--port', '5678', '--file', filename, ], env={ From a4c0c328926fce2331f5b94f9bfa8cad89fa61b9 Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Wed, 17 Oct 2018 19:31:35 -0700 Subject: [PATCH 3/4] Update README.md Fix snippets since `--host` is now required, and clarify things in general. Fix JSON highlighting. --- README.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 765e8760..eaf8b589 100644 --- a/README.md +++ b/README.md @@ -21,27 +21,36 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio ### Debug a script file Use this to launch your script file. Launch script file without waiting for debugger to attach. ```console --m ptvsd --port 5678 myfile.py +-m ptvsd --host localhost --port 5678 myfile.py ``` If you want the debugger to attach before running your code use `--wait` flag. ```console --m ptvsd --port 5678 --wait myfile.py +-m ptvsd --host localhost --port 5678 --wait myfile.py ``` +To attach from another machine, make sure that the server is listening on a public interface - using `0.0.0.0` will make it listen on all available interfaces: +```console +-m ptvsd --host 0.0.0.0 --port 5678 myfile.py +``` +This should only be done on secure networks, since anyone who can connect to the specified port can then execute arbitrary code within the debugged process. + +To pass arguments to the script, just specify them after the filename. This works the same as with Python itself - everything up to the filename is processed by ptvsd, but everything after that becomes `sys.argv` of the running process. ### Debug a module Use this to launch your module. Launch script file without waiting for debugger to attach. ```console --m ptvsd --port 5678 -m mymodule +-m ptvsd --host localhost --port 5678 -m mymodule ``` If you want the debugger to attach before running your code use `--wait` flag. ```console --m ptvsd --port 5678 --wait -m mymodule +-m ptvsd --host localhost --port 5678 --wait -m mymodule ``` +Same as with scripts, command line arguments can be passed to the module by specifying them after the module name. -### Debug a process by id -Attach to a process running python code. + +### Attach to a running process by ID +Injects the debugger into a process with a given PID that is running Python code. Once this command returns, a ptvsd server is running within the process, as if it were launched via `-m ptvd` itself. ```console --m ptvsd --host 0.0.0.0 --port 5678 --pid 12345 +-m ptvsd --host localhost --port 5678 --pid 12345 ``` ## `ptvsd` Import usage @@ -49,7 +58,6 @@ Attach to a process running python code. In your script import ptvsd and call `enable_attach` to enable the process to attach to the debugger. The default port is 5678. You can configure this while calling `enable_attach`. ```python import ptvsd - ptvsd.enable_attach() # your code @@ -58,10 +66,8 @@ ptvsd.enable_attach() Use the `wait_for_attach()` function to block execution until debugger is attached. ```python import ptvsd - ptvsd.enable_attach() -# script execution will stop here till debugger is attached -ptvsd.wait_for_attach() +ptvsd.wait_for_attach() # script execution will stop here till debugger is attached # your code ``` @@ -70,7 +76,6 @@ ptvsd.wait_for_attach() In python >= 3.7, `ptvsd` supports the `breakpoint()` function. Use `break_into_debugger()` function for similar behavior and compatibility with older versions of python (2.7 and >= 3.4). These functions will block only if the debugger is attached. ```python import ptvsd - ptvsd.enable_attach() while True: @@ -81,7 +86,7 @@ while True: ## Custom Protocol arguments ### Launch request arguments -```json +```json5 { "debugOptions": [ "RedirectOutput", // Whether to redirect stdout and stderr (see pydevd_comm.CMD_REDIRECT_OUTPUT) @@ -98,7 +103,7 @@ while True: ``` ### Attach request arguments -```json +```json5 { "debugOptions": [ "RedirectOutput", // Whether to redirect stdout and stderr (see pydevd_comm.CMD_REDIRECT_OUTPUT) From 5197e1e73f1cc06a3738f88e84e026b3ef9b9efb Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Wed, 17 Oct 2018 19:46:39 -0700 Subject: [PATCH 4/4] Update README.md More improvements. --- README.md | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index eaf8b589..0f5b9181 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ For more information see the [Code of Conduct FAQ](https://opensource.microsoft. contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## `ptvsd` CLI Usage -### Debug a script file -Use this to launch your script file. Launch script file without waiting for debugger to attach. +### Debugging a script file +To run a script file with debugging enabled, but without waiting for the debugger to attach (i.e. code starts executing immediately): ```console -m ptvsd --host localhost --port 5678 myfile.py ``` -If you want the debugger to attach before running your code use `--wait` flag. +To wait until the debugger attaches before running your code, use the `--wait` switch. ```console -m ptvsd --host localhost --port 5678 --wait myfile.py ``` -To attach from another machine, make sure that the server is listening on a public interface - using `0.0.0.0` will make it listen on all available interfaces: +The `--host` option specifies the interface on which the debug server is listening for connections. To be able to attach from another machine, make sure that the server is listening on a public interface - using `0.0.0.0` will make it listen on all available interfaces: ```console -m ptvsd --host 0.0.0.0 --port 5678 myfile.py ``` @@ -35,53 +35,47 @@ This should only be done on secure networks, since anyone who can connect to the To pass arguments to the script, just specify them after the filename. This works the same as with Python itself - everything up to the filename is processed by ptvsd, but everything after that becomes `sys.argv` of the running process. -### Debug a module -Use this to launch your module. Launch script file without waiting for debugger to attach. +### Debugging a module +To run a module, use the `-m` switch instead of filename: ```console -m ptvsd --host localhost --port 5678 -m mymodule ``` -If you want the debugger to attach before running your code use `--wait` flag. -```console --m ptvsd --host localhost --port 5678 --wait -m mymodule -``` -Same as with scripts, command line arguments can be passed to the module by specifying them after the module name. +Same as with scripts, command line arguments can be passed to the module by specifying them after the module name. All other ptvsd switches work identically in this mode; in particular, `--wait` can be used to block execution until debugger attaches. - -### Attach to a running process by ID -Injects the debugger into a process with a given PID that is running Python code. Once this command returns, a ptvsd server is running within the process, as if it were launched via `-m ptvd` itself. +### Attaching to a running process by ID +The following command injects the debugger into a process with a given PID that is running Python code. Once the command returns, a ptvsd server is running within the process, as if that process was launched via `-m ptvsd` itself. ```console -m ptvsd --host localhost --port 5678 --pid 12345 ``` ## `ptvsd` Import usage -### Enable debugging -In your script import ptvsd and call `enable_attach` to enable the process to attach to the debugger. The default port is 5678. You can configure this while calling `enable_attach`. +### Enabling debugging +At the beginning of your script, import ptvsd, and call `ptvsd.enable_attach()` to start the debug server. The default hostname is `0.0.0.0`, and the default port is 5678; these can be overridden by passing a `(host, port)` tuple as the first argument of `enable_attach()`. ```python import ptvsd ptvsd.enable_attach() - -# your code +... ``` -### Wait for attach -Use the `wait_for_attach()` function to block execution until debugger is attached. + +### Waiting for debugger to attach +Use the `ptvsd.wait_for_attach()` function to block program execution until debugger is attached. ```python import ptvsd ptvsd.enable_attach() -ptvsd.wait_for_attach() # script execution will stop here till debugger is attached - -# your code +ptvsd.wait_for_attach() # blocks execution until debugger is attached +... ``` ### `breakpoint()` function -In python >= 3.7, `ptvsd` supports the `breakpoint()` function. Use `break_into_debugger()` function for similar behavior and compatibility with older versions of python (2.7 and >= 3.4). These functions will block only if the debugger is attached. +In Python 3.7 and above, `ptvsd` supports the standard `breakpoint()` function. Use `ptvsd.break_into_debugger()` function for similar behavior and compatibility with older versions of Python (3.6 and below). If the debugger is attached when either of these functions are invoked, it will pause execution on the calling line, as if it had a breakpoint set. If there's no debugger attached, the functions do nothing, and code continues to execute normally. ```python import ptvsd ptvsd.enable_attach() while True: - # your code - breakpoint() # ptvsd.break_into_debugger() - # your code + ... + breakpoint() # or ptvsd.break_into_debugger() on <3.7 + ... ``` ## Custom Protocol arguments