tombulled / innertube

Python Client for Google's Private InnerTube API. Works with YouTube, YouTube Music and more!
https://pypi.org/project/innertube/
MIT License
294 stars 20 forks source link

🧵 Async Client for InnerTube #67

Open dmozh opened 8 months ago

dmozh commented 8 months ago

Hi, thank for this lib, its amazing. I want to create wrapper for this lib to my custom sdk, but I noticed that innertube does not have an asynchronous interface. I have researched the code, and I see that httpx supports async interface with AsyncClient. After small modification (which not affect main codebase) i wrote async interface with usage AsyncClient which allows to work in async.

For example:

This sync client in async usage

async def task(client: InnerTube, i):
    print(f'task {i} start', 'sleep s', 5)
    await asyncio.sleep(5)
    client.search("arctic monkeys", params=PARAMS_TYPE_PLAYLIST)
    print(f'task {i} end')

async def main() -> None:
    start = datetime.datetime.now().timestamp()
    client = InnerTube("WEB", "2.20230920.00.00")
    tasks = []
    for i in range(10):
        tasks.append(asyncio.ensure_future(task(client, i)))
    await asyncio.gather(*tasks)
    end = datetime.datetime.now().timestamp()
    print(end-start)

asyncio.run(main())

and result

task 0 start sleep s 5
task 1 start sleep s 5
task 2 start sleep s 5
task 3 start sleep s 5
task 4 start sleep s 5
task 5 start sleep s 5
task 6 start sleep s 5
task 7 start sleep s 5
task 8 start sleep s 5
task 9 start sleep s 5
task 0 end
task 2 end
task 6 end
task 9 end
task 8 end
task 5 end
task 7 end
task 4 end
task 1 end
task 3 end
8.483278036117554

Here we see that event loop is blocked on every request, because every request is synchronous.

And example with async client

async def task(client: InnerTube, i):
    print(f'task {i} start','sleep s', 5)
    await asyncio.sleep(5)
    await client.search("arctic monkeys", params=PARAMS_TYPE_PLAYLIST)
    print(f'task {i} end')

async def main() -> None:
    start = datetime.datetime.now().timestamp()
    client = InnerTube("WEB", "2.20230920.00.00")
    tasks = []
    for i in range(10):
        tasks.append(asyncio.ensure_future(task(client, i)))
    await asyncio.gather(*tasks)
    end = datetime.datetime.now().timestamp()
    print(end-start)

asyncio.run(main())

and result

task 0 start sleep s 5
task 1 start sleep s 5
task 2 start sleep s 5
task 3 start sleep s 5
task 4 start sleep s 5
task 5 start sleep s 5
task 6 start sleep s 5
task 7 start sleep s 5
task 8 start sleep s 5
task 9 start sleep s 5
task 2 end
task 5 end
task 1 end
task 9 end
task 0 end
task 4 end
task 7 end
task 3 end
task 6 end
task 8 end
5.66729998588562

So, what do you think about it? If you intresting, i can create PR with include async interface.

tombulled commented 8 months ago

Hi @dmozh, thanks for opening this issue and thanks for your kind words! Supporting asynchronous requests is absolutely something this library should support, especially given that httpx supports this. It wasn't implemented from the start mostly due to a lack of knowledge of asynchronous Python on my end in all honesty.

I'd definitely be interested in a PR if you're able to spare the time, and will add async support to my roadmap for innertube!

dmozh commented 8 months ago

That good news!

I have already prepared changes and tests, but i cant to push them.

tombulled commented 6 months ago

Awesome! To contribute your changes, you should be able to fork this repository, cut a branch on your fork with your changes, then open a PR from that branch into this repository.

GitHub has a guide on this here and DigitalOcean has a good write-up on this process too here. Hope that helps! :slightly_smiling_face: