Skip to content

Commit 35ec7d5

Browse files
committed
Add async client generator
1 parent f36f87d commit 35ec7d5

File tree

9 files changed

+568
-136
lines changed

9 files changed

+568
-136
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Python Telegraph API wrapper
99

1010
```bash
1111
$ python3 -m pip install telegraph
12+
# with asyncio support
13+
$ python3 -m pip install 'telegraph[aio]'
1214
```
1315

1416
## Example
@@ -24,3 +26,22 @@ response = telegraph.create_page(
2426
)
2527
print(response['url'])
2628
```
29+
30+
## Async Example
31+
```python
32+
import asyncio
33+
from telegraph.aio import Telegraph
34+
35+
async def main():
36+
telegraph = Telegraph()
37+
print(await telegraph.create_account(short_name='1337'))
38+
39+
response = await telegraph.create_page(
40+
'Hey',
41+
html_content='<p>Hello, world!</p>',
42+
)
43+
print(response['url'])
44+
45+
46+
asyncio.run(main())
47+
```

docs/index.rst

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,53 @@ Installation
66

77
.. code-block:: shell-session
88
9-
$ pip install telegraph
9+
$ python3 -m pip install telegraph
10+
# with asyncio support
11+
$ python3 -m pip install 'telegraph[aio]'
1012
1113
Example
1214
-------
1315

1416
.. code-block:: python
1517
16-
from telegraph import Telegraph
18+
from telegraph import Telegraph
1719
18-
telegraph = Telegraph()
20+
telegraph = Telegraph()
21+
telegraph.create_account(short_name='1337')
1922
20-
telegraph.create_account(short_name='1337')
23+
response = telegraph.create_page(
24+
'Hey',
25+
html_content='<p>Hello, world!</p>'
26+
)
27+
print(response['url'])
2128
22-
response = telegraph.create_page(
23-
'Hey',
24-
html_content='<p>Hello, world!</p>'
25-
)
29+
Async Example
30+
-------------
2631

27-
print('https://telegra.ph/{}'.format(response['path']))
32+
.. code-block:: python
33+
34+
import asyncio
35+
from telegraph.aio import Telegraph
36+
37+
async def main():
38+
telegraph = Telegraph()
39+
print(await telegraph.create_account(short_name='1337'))
40+
41+
response = await telegraph.create_page(
42+
'Hey',
43+
html_content='<p>Hello, world!</p>',
44+
)
45+
print(response['url'])
46+
47+
48+
asyncio.run(main())
2849
2950
3051
.. toctree::
31-
:maxdepth: 4
32-
:caption: Contents:
52+
:maxdepth: 4
53+
:caption: Contents:
3354

34-
telegraph
55+
telegraph
3556

3657

3758
Indices and tables

generate_async_api.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Generate async api from sync api"""
2+
from typing import Optional
3+
4+
import libcst as cst
5+
from libcst._nodes.expression import Await
6+
from libcst._nodes.whitespace import SimpleWhitespace
7+
8+
9+
class SyncToAsyncTransformer(cst.CSTTransformer):
10+
def __init__(self):
11+
self.stack = []
12+
self.fn_should_async = None
13+
14+
# PATH MAKING
15+
def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]:
16+
self.stack.append(node.name.value)
17+
18+
def leave_ClassDef(
19+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
20+
) -> cst.CSTNode:
21+
self.stack.pop()
22+
return updated_node
23+
24+
def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]:
25+
self.stack.append(node.name.value)
26+
27+
# END PATH MAKING
28+
29+
def leave_ImportAlias(
30+
self, original_node: cst.ImportAlias, updated_node: cst.ImportAlias
31+
) -> cst.CSTNode:
32+
"""Replace requests import with httpx"""
33+
34+
if original_node.name.value == "requests":
35+
return updated_node.with_changes(
36+
name=cst.Name("httpx"),
37+
)
38+
39+
return updated_node
40+
41+
def leave_Attribute(
42+
self, original_node: cst.Attribute, updated_node: cst.Attribute
43+
) -> cst.CSTNode:
44+
"""Replace requests attrs with httpx attrs"""
45+
46+
if (
47+
isinstance(original_node.value, cst.Name)
48+
and original_node.value.value == "requests"
49+
):
50+
mapping = {"Session": "AsyncClient"}
51+
52+
return updated_node.with_changes(
53+
value=cst.Name("httpx"),
54+
attr=cst.Name(mapping[original_node.attr.value]),
55+
)
56+
57+
return updated_node
58+
59+
def leave_Call(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef):
60+
"""Await calls to `method` of TelegraphApi"""
61+
path = []
62+
63+
a = original_node.func
64+
while isinstance(a, cst.Attribute) or isinstance(a, cst.Name):
65+
if isinstance(a, cst.Attribute):
66+
path.append(a.attr.value)
67+
else:
68+
path.append(a.value)
69+
a = a.value
70+
71+
# await the call if it's API class method
72+
should_await = (
73+
path[-2:] == ["session", "self"]
74+
or path[-3:] == [
75+
"method",
76+
"_telegraph",
77+
"self",
78+
]
79+
or path[-3:] == [
80+
"upload_file",
81+
"_telegraph",
82+
"self",
83+
]
84+
)
85+
if not should_await:
86+
return updated_node
87+
88+
self.fn_should_async = self.stack # mark current fn as async on leave
89+
# await the call
90+
return Await(
91+
updated_node,
92+
lpar=[cst.LeftParen()],
93+
rpar=[cst.RightParen()],
94+
)
95+
96+
def leave_FunctionDef(
97+
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
98+
):
99+
should_async = self.stack == self.fn_should_async
100+
self.fn_should_async = None
101+
self.stack.pop()
102+
103+
if not should_async:
104+
return updated_node
105+
106+
# mark fn as async
107+
return updated_node.with_changes(
108+
asynchronous=cst.Asynchronous()
109+
)
110+
111+
112+
def main():
113+
with open("telegraph/api.py") as f:
114+
py_source = f.read()
115+
116+
source_tree = cst.parse_module(py_source)
117+
118+
transformer = SyncToAsyncTransformer()
119+
modified_tree = source_tree.visit(transformer)
120+
121+
with open("telegraph/aio.py", "w") as f:
122+
f.write(modified_tree.code)
123+
124+
125+
if __name__ == "__main__":
126+
main()

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
requests
1+
requests
2+
httpx
3+
libcst

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141

4242
packages=['telegraph'],
4343
install_requires=['requests'],
44+
extras_require={
45+
'aio': ['httpx'],
46+
},
4447

4548
classifiers=[
4649
'License :: OSI Approved :: MIT License',

0 commit comments

Comments
 (0)