Skip to content

Commit 5d2ae72

Browse files
fix(stealth-diagnostic): stop sync wrapper from shadowing the subpackage (#10)
The sync convenience wrapper was exported as `stealth_diagnostic`, the same name as the `mithwire.stealth_diagnostic` subpackage. Binding it on the `mithwire` namespace overwrote the subpackage attribute, so `import mithwire.stealth_diagnostic.<x> as alias` (which compiles to IMPORT_FROM and walks that attribute) raised "cannot import name '<x>' from 'stealth_diagnostic' (unknown location)". The `from mithwire.stealth_diagnostic.<x> import ...` form happened to work, which masked the bug. Rename the sync wrapper to `diagnose_stealth`. The async `run_stealth_diagnostic` and the `mithwire.stealth_diagnostic.*` import paths are unchanged, so existing consumers (incl. mithwire-mcp) are unaffected. Add regression tests asserting `mithwire.stealth_diagnostic` is the module and that the dotted submodule import resolves. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7285395 commit 5d2ae72

4 files changed

Lines changed: 32 additions & 10 deletions

File tree

mithwire/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from mithwire.core.tab import Tab
1717
from mithwire.core.util import loop, start
1818
from mithwire.stealth import FingerprintConfig, Stealth, compute_launch_args
19-
from mithwire.stealth_diagnostic import run_stealth_diagnostic, stealth_diagnostic
19+
from mithwire.stealth_diagnostic import diagnose_stealth, run_stealth_diagnostic
2020

2121
__all__ = [
2222
"loop",
@@ -32,7 +32,7 @@
3232
"FingerprintConfig",
3333
"Stealth",
3434
"compute_launch_args",
35-
"stealth_diagnostic",
35+
"diagnose_stealth",
3636
"run_stealth_diagnostic",
3737
]
3838

mithwire/stealth_diagnostic/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
public bot-detection sites and reports how it looks, so anyone who installs
55
mithwire can check their machine, adjust their client, and re-run to verify.
66
7-
from mithwire import stealth_diagnostic
8-
report = stealth_diagnostic() # headful, all sites
7+
from mithwire import diagnose_stealth
8+
report = diagnose_stealth() # headful, all sites (sync)
99
print(report.verdict) # PASS / WARN / FAIL
1010
1111
Or from the shell::
@@ -23,10 +23,10 @@
2323
build_report,
2424
format_report,
2525
)
26-
from .runner import run_stealth_diagnostic, stealth_diagnostic
26+
from .runner import diagnose_stealth, run_stealth_diagnostic
2727

2828
__all__ = [
29-
"stealth_diagnostic",
29+
"diagnose_stealth",
3030
"run_stealth_diagnostic",
3131
"build_report",
3232
"format_report",

mithwire/stealth_diagnostic/runner.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .probes import NAV_PROBE, SITES, WEBRTC_PROBE, parse, wrap
1717
from .report import StealthDiagnosticReport, build_report
1818

19-
__all__ = ["run_stealth_diagnostic", "stealth_diagnostic"]
19+
__all__ = ["run_stealth_diagnostic", "diagnose_stealth"]
2020

2121

2222
async def _guard(coro, timeout: float, name: str) -> Any:
@@ -102,6 +102,12 @@ async def run_stealth_diagnostic(
102102
return build_report(raw, headless=headless)
103103

104104

105-
def stealth_diagnostic(**kwargs: Any) -> StealthDiagnosticReport:
106-
"""Synchronous wrapper around :func:`run_stealth_diagnostic` (engine loop)."""
105+
def diagnose_stealth(**kwargs: Any) -> StealthDiagnosticReport:
106+
"""Synchronous wrapper around :func:`run_stealth_diagnostic` (engine loop).
107+
108+
Named ``diagnose_stealth`` (not ``stealth_diagnostic``) on purpose: the
109+
package is ``mithwire.stealth_diagnostic``, so a same-named callable would
110+
shadow the subpackage on the ``mithwire`` namespace and break
111+
``import mithwire.stealth_diagnostic.<x>``.
112+
"""
107113
return _loop().run_until_complete(run_stealth_diagnostic(**kwargs))

tests/test_stealth_diagnostic_cli.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212
from __future__ import annotations
1313

14+
import types
1415
import unittest
1516

1617
import mithwire
@@ -20,7 +21,7 @@
2021

2122
class PublicApiTests(unittest.TestCase):
2223
def test_package_exports_are_callable(self):
23-
for name in ("stealth_diagnostic", "run_stealth_diagnostic"):
24+
for name in ("diagnose_stealth", "run_stealth_diagnostic"):
2425
self.assertIn(name, mithwire.__all__)
2526
self.assertTrue(callable(getattr(mithwire, name)))
2627

@@ -29,6 +30,21 @@ def test_old_selftest_name_is_gone(self):
2930
self.assertFalse(hasattr(mithwire, "selftest"))
3031
self.assertFalse(hasattr(mithwire, "run_selftest"))
3132

33+
def test_subpackage_attribute_is_the_module_not_a_function(self):
34+
# Regression: the sync wrapper used to be exported as ``stealth_diagnostic``,
35+
# which shadowed the ``mithwire.stealth_diagnostic`` subpackage on the
36+
# ``mithwire`` namespace and broke dotted submodule imports below.
37+
self.assertIsInstance(mithwire.stealth_diagnostic, types.ModuleType)
38+
39+
def test_dotted_submodule_import_resolves(self):
40+
# ``import mithwire.stealth_diagnostic.probes as p`` compiles to IMPORT_FROM,
41+
# which walks the (formerly shadowed) ``stealth_diagnostic`` attribute. This
42+
# is the exact form that regressed; assert it resolves to the real module.
43+
import mithwire.stealth_diagnostic.probes as p
44+
45+
self.assertIs(p, mithwire.stealth_diagnostic.probes)
46+
self.assertTrue(p.SITES)
47+
3248

3349
class CliParserTests(unittest.TestCase):
3450
def setUp(self):

0 commit comments

Comments
 (0)