Skip to content

Commit

Permalink
Add async client generator
Browse files Browse the repository at this point in the history
  • Loading branch information
python273 committed Nov 5, 2021
1 parent f36f87d commit 35ec7d5
Show file tree
Hide file tree
Showing 9 changed files with 568 additions and 136 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Python Telegraph API wrapper

```bash
$ python3 -m pip install telegraph
# with asyncio support
$ python3 -m pip install 'telegraph[aio]'
```

## Example
Expand All @@ -24,3 +26,22 @@ response = telegraph.create_page(
)
print(response['url'])
```

## Async Example
```python
import asyncio
from telegraph.aio import Telegraph

async def main():
telegraph = Telegraph()
print(await telegraph.create_account(short_name='1337'))

response = await telegraph.create_page(
'Hey',
html_content='<p>Hello, world!</p>',
)
print(response['url'])


asyncio.run(main())
```
45 changes: 33 additions & 12 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,53 @@ Installation

.. code-block:: shell-session
$ pip install telegraph
$ python3 -m pip install telegraph
# with asyncio support
$ python3 -m pip install 'telegraph[aio]'
Example
-------

.. code-block:: python
from telegraph import Telegraph
from telegraph import Telegraph
telegraph = Telegraph()
telegraph = Telegraph()
telegraph.create_account(short_name='1337')
telegraph.create_account(short_name='1337')
response = telegraph.create_page(
'Hey',
html_content='<p>Hello, world!</p>'
)
print(response['url'])
response = telegraph.create_page(
'Hey',
html_content='<p>Hello, world!</p>'
)
Async Example
-------------

print('https://telegra.ph/{}'.format(response['path']))
.. code-block:: python
import asyncio
from telegraph.aio import Telegraph
async def main():
telegraph = Telegraph()
print(await telegraph.create_account(short_name='1337'))
response = await telegraph.create_page(
'Hey',
html_content='<p>Hello, world!</p>',
)
print(response['url'])
asyncio.run(main())
.. toctree::
:maxdepth: 4
:caption: Contents:
:maxdepth: 4
:caption: Contents:

telegraph
telegraph


Indices and tables
Expand Down
126 changes: 126 additions & 0 deletions generate_async_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Generate async api from sync api"""
from typing import Optional

import libcst as cst
from libcst._nodes.expression import Await
from libcst._nodes.whitespace import SimpleWhitespace


class SyncToAsyncTransformer(cst.CSTTransformer):
def __init__(self):
self.stack = []
self.fn_should_async = None

# PATH MAKING
def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]:
self.stack.append(node.name.value)

def leave_ClassDef(
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
) -> cst.CSTNode:
self.stack.pop()
return updated_node

def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]:
self.stack.append(node.name.value)

# END PATH MAKING

def leave_ImportAlias(
self, original_node: cst.ImportAlias, updated_node: cst.ImportAlias
) -> cst.CSTNode:
"""Replace requests import with httpx"""

if original_node.name.value == "requests":
return updated_node.with_changes(
name=cst.Name("httpx"),
)

return updated_node

def leave_Attribute(
self, original_node: cst.Attribute, updated_node: cst.Attribute
) -> cst.CSTNode:
"""Replace requests attrs with httpx attrs"""

if (
isinstance(original_node.value, cst.Name)
and original_node.value.value == "requests"
):
mapping = {"Session": "AsyncClient"}

return updated_node.with_changes(
value=cst.Name("httpx"),
attr=cst.Name(mapping[original_node.attr.value]),
)

return updated_node

def leave_Call(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef):
"""Await calls to `method` of TelegraphApi"""
path = []

a = original_node.func
while isinstance(a, cst.Attribute) or isinstance(a, cst.Name):
if isinstance(a, cst.Attribute):
path.append(a.attr.value)
else:
path.append(a.value)
a = a.value

# await the call if it's API class method
should_await = (
path[-2:] == ["session", "self"]
or path[-3:] == [
"method",
"_telegraph",
"self",
]
or path[-3:] == [
"upload_file",
"_telegraph",
"self",
]
)
if not should_await:
return updated_node

self.fn_should_async = self.stack # mark current fn as async on leave
# await the call
return Await(
updated_node,
lpar=[cst.LeftParen()],
rpar=[cst.RightParen()],
)

def leave_FunctionDef(
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
):
should_async = self.stack == self.fn_should_async
self.fn_should_async = None
self.stack.pop()

if not should_async:
return updated_node

# mark fn as async
return updated_node.with_changes(
asynchronous=cst.Asynchronous()
)


def main():
with open("telegraph/api.py") as f:
py_source = f.read()

source_tree = cst.parse_module(py_source)

transformer = SyncToAsyncTransformer()
modified_tree = source_tree.visit(transformer)

with open("telegraph/aio.py", "w") as f:
f.write(modified_tree.code)


if __name__ == "__main__":
main()
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
requests
requests
httpx
libcst
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@

packages=['telegraph'],
install_requires=['requests'],
extras_require={
'aio': ['httpx'],
},

classifiers=[
'License :: OSI Approved :: MIT License',
Expand Down
Loading

0 comments on commit 35ec7d5

Please sign in to comment.