feat(client): make dir()/help() work through _SyncProxy

The sync REPL proxy forwarded methods via __getattr__, so dir()/tab-completion never saw them and the sync wrapper reported (*args, **kwargs) instead of the real signature. Add __dir__ delegating to the wrapped target, and decorate the async wrapper with functools.wraps so inspect.signature()/help() report the real params + docstring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-05 00:20:03 -07:00
parent a90a01f2aa
commit 739e28e113
2 changed files with 41 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
"""Main Proxmark3 client with nested API."""
import asyncio
import functools
import glob
import inspect
import logging
@@ -161,13 +162,20 @@ class _SyncProxy:
if hasattr(attr, '_t'):
return _SyncProxy(attr, self._loop)
if callable(attr) and inspect.iscoroutinefunction(attr):
# functools.wraps copies __doc__/__name__ AND sets __wrapped__, so
# inspect.signature() and help() report the real params (block, uid=None)
# instead of the wrapper's (*args, **kwargs).
@functools.wraps(attr)
def sync_wrapper(*args, **kwargs):
return self._loop.run_until_complete(attr(*args, **kwargs))
sync_wrapper.__name__ = name
sync_wrapper.__doc__ = attr.__doc__
return sync_wrapper
return attr
def __dir__(self):
# Surface the wrapped target's command methods and sub-modules so
# dir()/REPL tab-completion and help() discovery work through the proxy.
return sorted(set(object.__dir__(self)) | set(dir(self._target)))
def close(self):
"""Disconnect and close the event loop."""
self._loop.run_until_complete(self._target.disconnect())