Skip to content

Add more typing to debugpy and switch to 'standard' type checking mode#1637

Open
rchiodo wants to merge 27 commits into
microsoft:mainfrom
rchiodo:rchiodo/type_standard
Open

Add more typing to debugpy and switch to 'standard' type checking mode#1637
rchiodo wants to merge 27 commits into
microsoft:mainfrom
rchiodo:rchiodo/type_standard

Conversation

@rchiodo

@rchiodo rchiodo commented Jul 24, 2024

Copy link
Copy Markdown
Contributor

This is entirely necessary but I was using this to test Pylance. Seems like a good thing to be more strictly typed though.

@rchiodo
rchiodo requested a review from a team as a code owner July 24, 2024 21:34
Comment thread src/debugpy/adapter/clients.py Outdated
Comment thread src/debugpy/adapter/clients.py Outdated
Comment thread src/debugpy/common/messaging.py Outdated
Comment thread src/debugpy/common/messaging.py Outdated
debonte
debonte previously approved these changes Jul 24, 2024
…odo/type_standard

# Conflicts:
#	CONTRIBUTING.md
#	src/debugpy/adapter/clients.py
#	src/debugpy/adapter/launchers.py
#	src/debugpy/adapter/servers.py
#	src/debugpy/common/sockets.py
#	src/debugpy/server/api.py
#	src/debugpy/server/cli.py
#	tests/requirements.txt
@rchiodo
rchiodo requested a review from a team as a code owner July 16, 2026 19:00
@heejaechang

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — @heejaechang is auto-reviewing this PR.

Comment thread src/debugpy/common/messaging.py
Comment thread src/debugpy/common/messaging.py Outdated
Comment thread src/debugpy/common/util.py Outdated
Comment thread src/debugpy/common/json.py
Comment thread src/debugpy/server/api.py Outdated
Comment thread src/debugpy/adapter/servers.py Outdated
Comment thread src/debugpy/common/messaging.py
Comment thread pyproject.toml
@heejaechang

Copy link
Copy Markdown
Contributor

Mostly a solid typing pass, but a few runtime-behavior changes rode in with it and two are genuine bugs (Message.__call__ no-arg path and wait_for_response(raise_if_failed=False)). Please fix those, reconsider the mutable class-default and settrace-latch changes, and add targeted tests for the behavior changes.

- messaging.Message.__call__: fix dead no-arg fast path (args.count -> len(args))
- messaging.OutgoingRequest.wait_for_response: honor raise_if_failed=False by
  returning the error body instead of asserting; return type now MessageDict|Exception
- messaging: complete AssociableMessageDict migration; associate_with is now a real
  method backed by a shared associated_dicts list instead of a setattr closure
- server/api._settrace: only latch called on success so a failed settrace no
  longer permanently blocks configure(); drop no-op except/finally
- adapter/servers.authenticate: fail closed when authorize result is an Exception
- common/util.Observable.observers: revert class default to immutable tuple to avoid
  shared-mutable footgun; typed and callers reassign instead of in-place +=
- common/json._converter: document the int/float narrowing from numbers.Number
- add unit tests for wait_for_response(raise_if_failed=False), Message.__call__()
  no-arg, and the configure() already-running guard incl. the settrace-failure case

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/debugpy/adapter/launchers.py
@heejaechang

Copy link
Copy Markdown
Contributor

Nice PR — the risky runtime changes (settrace/configure gate, wait_for_response semantics, no-arg Message.call, fail-closed auth) are backed by targeted tests, and several concerns a reviewer might raise are already handled with inline rationale. Approving; one small defensive-guard note left inline.

@heejaechang heejaechang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Restore fail-fast behavior in spawn_debuggee: for a real debug launch,
assert servers.listener is not None instead of silently skipping the
port/adapterAccessToken setup, which would spawn a debuggee unable to
connect back.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/debugpy/common/singleton.py
Comment thread src/debugpy/adapter/clients.py Outdated
Comment thread src/debugpy/adapter/servers.py Outdated
@heejaechang

Copy link
Copy Markdown
Contributor

Overall this is a solid, well-tested typing pass. A few non-blocking notes below about failing-fast vs. substituting degraded fallback values on the newly-added None paths, plus one latent -O-mode crash path in servers.py. Nothing here blocks merge.

@heejaechang heejaechang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Address review feedback on the typing pass: replace silently-degraded None fallbacks with fail-fast asserts, and make ThreadSafeSingleton.assert_locked side-effect-free.

- singleton.py: assert_locked now checks lock._is_owned() instead of acquire()/release(), avoiding RLock recursion-count mutation and an unbalanced release() under python -O.

- servers.py: assert pydevdSystemInfo result is non-exception (pid/ppid always assigned before use); assert listener in inject() rather than connecting to an empty address.

- clients.py: assert servers.listener in attach_request and the client listener in notify_of_subprocess instead of substituting ("", 0)/None.

- debuggee.py: assert process in wait_for_exit rather than reporting a bogus clean exit (code 0).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
body = request.wait_for_response(raise_if_failed=False)
assert isinstance(body, messaging.MessageHandlingError)
assert body is request.response.body
assert "pause not supported" in str(body)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert "pause not supported" in str(body) is a partial substring check. MessageHandlingError.__str__ returns the bare reason, so assert it exactly: assert str(body) == "pause not supported". Per the repo's tests-no-partial-asserts rule.

monkeypatch.setattr(api.pydevd, "settrace", failing_settrace)

with pytest.raises(RuntimeError, match="settrace failed"):
api._settrace()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest.raises(RuntimeError, match="settrace failed") uses substring/regex matching. The message is controlled by the test itself, so assert it exactly:

with pytest.raises(RuntimeError) as exc_info:
    api._settrace()
assert str(exc_info.value) == "settrace failed"

Per the repo's tests-no-partial-asserts rule.

monkeypatch.setattr(api.pydevd, "settrace", lambda *args, **kwargs: None)

api._settrace()
assert api._settrace.called is True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest.raises(RuntimeError, match="already running") matches the superstring "debug adapter is already running". Assert the exact message instead:

with pytest.raises(RuntimeError) as exc_info:
    api.configure()
assert str(exc_info.value) == "debug adapter is already running"

Per the repo's tests-no-partial-asserts rule.

Comment thread src/debugpy/server/api.py

def configure(properties=None, **kwargs):
if getattr(_settrace, "called"):
raise RuntimeError("debug adapter is already running")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_settrace.called is a process-global latch that is only ever set True and never cleared. The new configure() guard therefore permanently raises "debug adapter is already running" for the interpreter's lifetime, so any legitimate reconfigure/re-attach after disconnect in a long-lived process is now blocked (previously a second configure() proceeded). Confirm whether re-attach-after-disconnect is supported — if so, clear the latch on disconnect/teardown; if not, document the one-shot contract (the tests only cover first-call behavior).

@heejaechang

Copy link
Copy Markdown
Contributor

Overall the added typing and the _settrace success-latching fix look good. Two things to resolve before merge: (1) tighten the new/changed test assertions to exact-match per the repo's partial-assert rule, and (2) confirm the intended contract for the new configure() "already running" guard, since _settrace.called is a process-global latch that is never cleared.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants