Skip to content

Commit

Permalink
refactor: async support for cli
Browse files Browse the repository at this point in the history
typer officially does not support asyncio, there's a thread about it
fastapi/typer#88

essentially we are using a decorator based workaround which has been
adapted from a solution posted by @gilcu2 on his comment
https://github.com/tiangolo/typer/issues/88\#issuecomment-1732469681

note that this requires the use of asyncer which uses a previous version
of anyio, i am willing to live with this for now until an official solution
is published by typer
  • Loading branch information
devraj committed Dec 18, 2023
1 parent 1dc72c1 commit 620b652
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 21 deletions.
8 changes: 4 additions & 4 deletions gallagher/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""
""" CLI entry point
"""
import typer

from gallagher import __version__

from .utils import AsyncTyper

from .alarms import app as alarms_app
from .cardholders import app as cardholders_app

# Main Typer app use to create the CLI
app = typer.Typer()
app = AsyncTyper()
app.add_typer(alarms_app, name="alarms")
app.add_typer(cardholders_app, name="ch")

Expand Down
7 changes: 4 additions & 3 deletions gallagher/cli/alarms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
"""

from gallagher import cc
import os
import typer

from gallagher import cc
from .utils import AsyncTyper

api_key = os.environ.get("GACC_API_KEY")

cc.api_key = api_key

app = typer.Typer()
app = AsyncTyper()
13 changes: 8 additions & 5 deletions gallagher/cli/cardholders.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
""" Cardholder cli commands mounted at ch
"""
import typer
from rich import print as rprint
from rich.console import Console
from rich.table import Table

from gallagher.cc.cardholders.cardholders import Cardholder
from .utils import AsyncTyper

app = typer.Typer(help="query or manage cardholders")
from gallagher.cc.cardholders.cardholders import (
Cardholder
)

app = AsyncTyper(help="query or manage cardholders")


@app.command("list")
Expand All @@ -20,7 +23,7 @@ async def list():
"[bold green]Fetching cardholders...",
spinner="clock"
):
cardholders = Cardholder.list()
cardholders = await Cardholder.list()

table = Table(title="Cardholders")
for header in cardholders.cli_header:
Expand All @@ -36,5 +39,5 @@ async def list():
async def get(id: int):
""" get a cardholder by id
"""
cardholder = Cardholder.retrieve(id)
cardholder = await Cardholder.retrieve(id)
[rprint(r) for r in cardholder.__rich_repr__()]
49 changes: 49 additions & 0 deletions gallagher/cli/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
""" Asyncio patches for Typer
There's a thread open on the typer repo about this:
https://github.com/tiangolo/typer/issues/88
Essentially, Typer doesn't support async functions. This is an issue
post migration to async, @csheppard points out that there's a work
around using a decorator on the click repository:
https://github.com/tiangolo/typer/issues/88#issuecomment-612687289
@gilcu2 posted a similar solution on the typer repo:
https://github.com/tiangolo/typer/issues/88#issuecomment-1732469681
this particular one uses asyncer to run the async function in a thread
we're going in with this with the hope that the official solution is
closer to this than a decorator per command.
"""
import inspect

from functools import (
partial,
wraps,
)

import asyncer
from typer import Typer


class AsyncTyper(Typer):
@staticmethod
def maybe_run_async(decorator, f):
if inspect.iscoroutinefunction(f):

@wraps(f)
def runner(*args, **kwargs):
return asyncer.runnify(f)(*args, **kwargs)

decorator(runner)
else:
decorator(f)
return f

def callback(self, *args, **kwargs):
decorator = super().callback(*args, **kwargs)
return partial(self.maybe_run_async, decorator)

def command(self, *args, **kwargs):
decorator = super().command(*args, **kwargs)
return partial(self.maybe_run_async, decorator)
30 changes: 22 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ typing-extensions = "^4.9.0"
coverage = "^7.3.3"
pytest = "^7.4.3"
pytest-order = "^1.2.0"
anyio = "^4.2.0"
annotated-types = "^0.6.0"
certifi = "^2023.11.17"
idna = "^3.6"
Expand All @@ -33,6 +32,7 @@ pluggy = "^1.3.0"
typer = {extras = ["all"], version = "^0.9.0"}
rich = "^13.7.0"
pytest-asyncio = "^0.23.2"
asyncer = "^0.0.2"

[tool.poetry.group.dev.dependencies]
pytest = "^7.2.2"
Expand Down

0 comments on commit 620b652

Please sign in to comment.