public_api.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # Copyright (c) Microsoft Corporation. All rights reserved.
  2. # Licensed under the MIT License. See LICENSE in the project root
  3. # for license information.
  4. from __future__ import annotations
  5. import dataclasses
  6. import functools
  7. import typing
  8. from debugpy import _version
  9. # Expose debugpy.server API from subpackage, but do not actually import it unless
  10. # and until a member is invoked - we don't want the server package loaded in the
  11. # adapter, the tests, or setup.py.
  12. # Docstrings for public API members must be formatted according to PEP 8 - no more
  13. # than 72 characters per line! - and must be readable when retrieved via help().
  14. Endpoint = typing.Tuple[str, int]
  15. @dataclasses.dataclass(frozen=True)
  16. class CliOptions:
  17. """Options that were passed to the debugpy CLI entry point."""
  18. mode: typing.Literal["connect", "listen"]
  19. target_kind: typing.Literal["file", "module", "code", "pid"]
  20. address: Endpoint
  21. log_to: str | None = None
  22. log_to_stderr: bool = False
  23. target: str | None = None
  24. wait_for_client: bool = False
  25. adapter_access_token: str | None = None
  26. config: dict[str, object] = dataclasses.field(default_factory=dict)
  27. parent_session_pid: int | None = None
  28. def _api(cancelable=False):
  29. def apply(f):
  30. @functools.wraps(f)
  31. def wrapper(*args, **kwargs):
  32. from debugpy.server import api
  33. wrapped = getattr(api, f.__name__)
  34. return wrapped(*args, **kwargs)
  35. if cancelable:
  36. def cancel(*args, **kwargs):
  37. from debugpy.server import api
  38. wrapped = getattr(api, f.__name__)
  39. return wrapped.cancel(*args, **kwargs)
  40. wrapper.cancel = cancel # pyright: ignore
  41. return wrapper
  42. return apply
  43. @_api()
  44. def log_to(__path: str | typing.TextIO) -> None:
  45. """Generate detailed debugpy logs in the specified directory.
  46. The directory must already exist. Several log files are generated,
  47. one for every process involved in the debug session.
  48. """
  49. @_api()
  50. def configure(__properties: dict[str, typing.Any] | None = None, **kwargs) -> None:
  51. """Sets debug configuration properties that cannot be set in the
  52. "attach" request, because they must be applied as early as possible
  53. in the process being debugged.
  54. For example, a "launch" configuration with subprocess debugging
  55. disabled can be defined entirely in JSON::
  56. {
  57. "request": "launch",
  58. "subProcess": false,
  59. ...
  60. }
  61. But the same cannot be done with "attach", because "subProcess"
  62. must be known at the point debugpy starts tracing execution. Thus,
  63. it is not available in JSON, and must be omitted::
  64. {
  65. "request": "attach",
  66. ...
  67. }
  68. and set from within the debugged process instead::
  69. debugpy.configure(subProcess=False)
  70. debugpy.listen(...)
  71. Properties to set can be passed either as a single dict argument,
  72. or as separate keyword arguments::
  73. debugpy.configure({"subProcess": False})
  74. """
  75. @_api()
  76. def listen(
  77. __endpoint: Endpoint | int, *, in_process_debug_adapter: bool = False
  78. ) -> Endpoint:
  79. """Starts a debug adapter debugging this process, that listens for
  80. incoming socket connections from clients on the specified address.
  81. `__endpoint` must be either a (host, port) tuple as defined by the
  82. standard `socket` module for the `AF_INET` address family, or a port
  83. number. If only the port is specified, host is "127.0.0.1".
  84. `in_process_debug_adapter`: by default a separate python process is
  85. spawned and used to communicate with the client as the debug adapter.
  86. By setting the value of `in_process_debug_adapter` to True a new
  87. python process is not spawned. Note: the con of setting
  88. `in_process_debug_adapter` to True is that subprocesses won't be
  89. automatically debugged.
  90. Returns the interface and the port on which the debug adapter is
  91. actually listening, in the same format as `__endpoint`. This may be
  92. different from address if port was 0 in the latter, in which case
  93. the adapter will pick some unused ephemeral port to listen on.
  94. This function does't wait for a client to connect to the debug
  95. adapter that it starts. Use `wait_for_client` to block execution
  96. until the client connects.
  97. """
  98. ...
  99. @_api()
  100. def connect(__endpoint: Endpoint | int, *, access_token: str | None = None, parent_session_pid: int | None = None) -> Endpoint:
  101. """Tells an existing debug adapter instance that is listening on the
  102. specified address to debug this process.
  103. `__endpoint` must be either a (host, port) tuple as defined by the
  104. standard `socket` module for the `AF_INET` address family, or a port
  105. number. If only the port is specified, host is "127.0.0.1".
  106. `access_token` must be the same value that was passed to the adapter
  107. via the `--server-access-token` command-line switch.
  108. `parent_session_pid` is the PID of the parent session to associate
  109. with. This is useful if running in a process that is not an immediate
  110. child of the parent process being debugged.
  111. This function does't wait for a client to connect to the debug
  112. adapter that it connects to. Use `wait_for_client` to block
  113. execution until the client connects.
  114. """
  115. ...
  116. @_api(cancelable=True)
  117. def wait_for_client() -> None:
  118. """If there is a client connected to the debug adapter that is
  119. debugging this process, returns immediately. Otherwise, blocks
  120. until a client connects to the adapter.
  121. While this function is waiting, it can be canceled by calling
  122. `wait_for_client.cancel()` from another thread.
  123. """
  124. @_api()
  125. def is_client_connected() -> bool:
  126. """True if a client is connected to the debug adapter that is
  127. debugging this process.
  128. """
  129. ...
  130. @_api()
  131. def breakpoint() -> None:
  132. """If a client is connected to the debug adapter that is debugging
  133. this process, pauses execution of all threads, and simulates a
  134. breakpoint being hit at the line following the call.
  135. It is also registered as the default handler for builtins.breakpoint().
  136. """
  137. @_api()
  138. def debug_this_thread() -> None:
  139. """Makes the debugger aware of the current thread.
  140. Must be called on any background thread that is started by means
  141. other than the usual Python APIs (i.e. the "threading" module),
  142. in order for breakpoints to work on that thread.
  143. """
  144. @_api()
  145. def trace_this_thread(__should_trace: bool):
  146. """Tells the debug adapter to enable or disable tracing on the
  147. current thread.
  148. When the thread is traced, the debug adapter can detect breakpoints
  149. being hit, but execution is slower, especially in functions that
  150. have any breakpoints set in them. Disabling tracing when breakpoints
  151. are not anticipated to be hit can improve performance. It can also
  152. be used to skip breakpoints on a particular thread.
  153. Tracing is automatically disabled for all threads when there is no
  154. client connected to the debug adapter.
  155. """
  156. def get_cli_options() -> CliOptions | None:
  157. """Returns the CLI options that were processed by debugpy.
  158. These options are all the options after the CLI args and
  159. environment variables that were processed on startup.
  160. If the debugpy CLI entry point was not called in this process, the
  161. returned value is None.
  162. """
  163. from debugpy.server import cli
  164. options = cli.options
  165. if options.mode is None or options.target_kind is None or options.address is None:
  166. # The CLI entrypoint was not called so there are no options present.
  167. return None
  168. # We don't return the actual options object because we don't want callers
  169. # to be able to mutate it. Instead we use a frozen dataclass as a snapshot
  170. # with richer type annotations.
  171. return CliOptions(
  172. mode=options.mode,
  173. target_kind=options.target_kind,
  174. address=options.address,
  175. log_to=options.log_to,
  176. log_to_stderr=options.log_to_stderr,
  177. target=options.target,
  178. wait_for_client=options.wait_for_client,
  179. adapter_access_token=options.adapter_access_token,
  180. config=options.config,
  181. parent_session_pid=options.parent_session_pid,
  182. )
  183. __version__: str = _version.get_versions()["version"]