Skip to content

Function resolution and public introspection API - #3

Merged
LifeLex merged 1 commit into
mainfrom
fix-resolution-and-public-instrospection-api
Jul 7, 2026
Merged

Function resolution and public introspection API #3
LifeLex merged 1 commit into
mainfrom
fix-resolution-and-public-instrospection-api

Conversation

@LifeLex

@LifeLex LifeLex commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Two changes driven by this discussion comment:

  1. Targeted protection now follows the function object, not the spelling of the patch target.
  2. New public API for downstream tools: resolve_do_not_mock(item) exposes the resolved marker state of any collected item.

Problem 1: aliased imports escaped protection

from myapp.payments import charge as my_charge creates a second name for the same function object. The guard compared patch targets by module identity + attribute name, so:

@pytest.mark.do_not_mock("myapp.payments.charge")
def test_flow():
    with patch("myapp.api.my_charge"):   # NOT blocked before this PR
        ...

The escape existed in both string mode and object mode, and the escaping spelling is the idiomatic one (unittest.mock docs: "patch where it's used").

Fix: string targets resolve to the live object via pkgutil.resolve_name at enforcement time (collection still imports no application code), and matches_patch_target checks getattr(target, attribute) is protected_obj first, keeping the module+name check as a fallback for unresolved targets.

Problem 2: no way for other tools to ask about the contract

The marker-stacking rules (union across scopes, bare marker wins, dedup) lived inline in the private hookwrapper. A test generator or another plugin wanting to respect the contract had to reimplement them.

Fix: new contract.py with resolve_do_not_mock(item) -> DoNotMockContract | None, safe to call at collection time. The hookwrapper is now a thin consumer of the same function. DoNotMockContract, ProtectedFunc and resolve_do_not_mock are exported from the package root.

Behavior changes

  • Breaking: an unresolvable string target (typo, missing module) now raises DoNotMockError at test time instead of silently protecting nothing.
  • New: dotted class-attribute paths are supported: @pytest.mark.do_not_mock("myapp.payments.PaymentGateway.charge").

@LifeLex
LifeLex merged commit a0e5013 into main Jul 7, 2026
14 checks passed
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.

1 participant