ipython-smart-await 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/.github/workflows/test.yml +1 -1
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/PKG-INFO +4 -3
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/README.md +3 -2
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/pyproject.toml +1 -1
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/smart_await/__init__.py +10 -1
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/tests/test_smart_await.py +16 -0
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/.github/workflows/release.yml +0 -0
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/.gitignore +0 -0
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/.pre-commit-config.yaml +0 -0
- {ipython_smart_await-0.1.0 → ipython_smart_await-0.2.0}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipython-smart-await
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Auto-await coroutine results (calls, subscripts, item-assignment) in the IPython REPL.
|
|
5
5
|
Project-URL: Homepage, https://github.com/doronz88/ipython-smart-await
|
|
6
6
|
Project-URL: Repository, https://github.com/doronz88/ipython-smart-await
|
|
@@ -51,6 +51,7 @@ Then, given some async API bound to `p`:
|
|
|
51
51
|
p.get_pid() # -> awaited automatically (no `await` needed)
|
|
52
52
|
a[0] # -> awaited if `a.__getitem__` returns a coroutine
|
|
53
53
|
a[0] = 7 # -> routed to `a.setindex(0, 7)` (awaited) when `a` has an async `setindex`
|
|
54
|
+
proxy.length # -> awaited if attribute access returns a coroutine (dynamic proxies)
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
### Opting out
|
|
@@ -67,8 +68,8 @@ Cells that already use `await` / `async` constructs are left untouched.
|
|
|
67
68
|
|
|
68
69
|
The extension installs an `ast` transformer (an IPython AST transformer) that wraps:
|
|
69
70
|
|
|
70
|
-
- **calls** (`foo()`)
|
|
71
|
-
it is a coroutine (non-coroutines pass through unchanged);
|
|
71
|
+
- **calls** (`foo()`), **subscript reads** (`a[0]`), and **attribute reads** (`a.b`) in a helper
|
|
72
|
+
that awaits the result only if it is a coroutine (non-coroutines pass through unchanged);
|
|
72
73
|
- **single-target subscript assignment** (`a[0] = v`) into a call that routes to an async
|
|
73
74
|
`setindex(key, value)` when the target exposes one, otherwise a normal item assignment — so
|
|
74
75
|
dicts, lists, numpy arrays, etc. are unaffected.
|
|
@@ -24,6 +24,7 @@ Then, given some async API bound to `p`:
|
|
|
24
24
|
p.get_pid() # -> awaited automatically (no `await` needed)
|
|
25
25
|
a[0] # -> awaited if `a.__getitem__` returns a coroutine
|
|
26
26
|
a[0] = 7 # -> routed to `a.setindex(0, 7)` (awaited) when `a` has an async `setindex`
|
|
27
|
+
proxy.length # -> awaited if attribute access returns a coroutine (dynamic proxies)
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
### Opting out
|
|
@@ -40,8 +41,8 @@ Cells that already use `await` / `async` constructs are left untouched.
|
|
|
40
41
|
|
|
41
42
|
The extension installs an `ast` transformer (an IPython AST transformer) that wraps:
|
|
42
43
|
|
|
43
|
-
- **calls** (`foo()`)
|
|
44
|
-
it is a coroutine (non-coroutines pass through unchanged);
|
|
44
|
+
- **calls** (`foo()`), **subscript reads** (`a[0]`), and **attribute reads** (`a.b`) in a helper
|
|
45
|
+
that awaits the result only if it is a coroutine (non-coroutines pass through unchanged);
|
|
45
46
|
- **single-target subscript assignment** (`a[0] = v`) into a call that routes to an async
|
|
46
47
|
`setindex(key, value)` when the target exposes one, otherwise a normal item assignment — so
|
|
47
48
|
dicts, lists, numpy arrays, etc. are unaffected.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ipython-smart-await"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Auto-await coroutine results (calls, subscripts, item-assignment) in the IPython REPL."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -24,7 +24,7 @@ from typing import Any
|
|
|
24
24
|
from IPython.core.interactiveshell import InteractiveShell
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
__version__ = "0.
|
|
27
|
+
__version__ = "0.2.0"
|
|
28
28
|
__all__ = ["load_ipython_extension"]
|
|
29
29
|
|
|
30
30
|
|
|
@@ -80,6 +80,15 @@ class _SmartAwaitTransformer(ast.NodeTransformer):
|
|
|
80
80
|
return visited_node
|
|
81
81
|
return ast.Call(func=ast.Name(_maybe_await.__name__, ctx=ast.Load()), args=[visited_node], keywords=[])
|
|
82
82
|
|
|
83
|
+
def visit_Attribute(self, node: ast.Attribute) -> ast.AST:
|
|
84
|
+
visited_node = self.generic_visit(node)
|
|
85
|
+
assert isinstance(visited_node, ast.expr)
|
|
86
|
+
# Only reads yield a (possibly awaitable) value; never wrap an assignment/deletion target.
|
|
87
|
+
# Supports objects that dispatch attribute access to a coroutine (e.g. dynamic proxies).
|
|
88
|
+
if not isinstance(node.ctx, ast.Load):
|
|
89
|
+
return visited_node
|
|
90
|
+
return ast.Call(func=ast.Name(_maybe_await.__name__, ctx=ast.Load()), args=[visited_node], keywords=[])
|
|
91
|
+
|
|
83
92
|
def visit_Assign(self, node: ast.Assign) -> ast.AST:
|
|
84
93
|
visited_node = self.generic_visit(node)
|
|
85
94
|
assert isinstance(visited_node, ast.Assign)
|
|
@@ -53,6 +53,10 @@ def test_subscript_read_is_wrapped(ip: InteractiveShell) -> None:
|
|
|
53
53
|
assert _rewrite(ip, "a[0]") == "_maybe_await(a[0])"
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def test_attribute_read_is_wrapped(ip: InteractiveShell) -> None:
|
|
57
|
+
assert _rewrite(ip, "a.length") == "_maybe_await(a.length)"
|
|
58
|
+
|
|
59
|
+
|
|
56
60
|
def test_subscript_assign_is_rewritten(ip: InteractiveShell) -> None:
|
|
57
61
|
assert _rewrite(ip, "a[0] = 7") == "_setitem(a, 0, 7)"
|
|
58
62
|
|
|
@@ -91,6 +95,18 @@ def test_subscript_assign_routes_to_setindex(ip: InteractiveShell) -> None:
|
|
|
91
95
|
assert a.store == {0: 7}
|
|
92
96
|
|
|
93
97
|
|
|
98
|
+
def test_attribute_auto_awaited(ip: InteractiveShell) -> None:
|
|
99
|
+
class Proxy:
|
|
100
|
+
def __getattr__(self, name):
|
|
101
|
+
async def _resolve():
|
|
102
|
+
return f"resolved:{name}"
|
|
103
|
+
|
|
104
|
+
return _resolve()
|
|
105
|
+
|
|
106
|
+
ip.user_ns["a"] = Proxy()
|
|
107
|
+
assert ip.run_cell("a.length").result == "resolved:length"
|
|
108
|
+
|
|
109
|
+
|
|
94
110
|
def test_dict_assign_unaffected(ip: InteractiveShell) -> None:
|
|
95
111
|
ip.user_ns["d"] = {}
|
|
96
112
|
assert ip.run_cell("d['k'] = 5").success
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|