Is there any way for a pex binary to run an async ...
# general
g
Is there any way for a pex binary to run an async function?
f
Do you mean as an entry point? In that case, no, entry points have to be sync but you can start your async event loop in you entry point and from there call all the async functions you want
g
Yeah easy - thanks
Out of curiosity why do they need to be sync?
e
This has nothing to do with Pex unless I misunderstand. These are Python rules you're discussing, right?
g
Isn't pex a python thing?
e
Yes, but it just runs Python code, it does not alter the rules of how Python code runs. Neither does a venv nor a vanilla pyz app, etc. So just narrowing down that this is not Pex related / not a Pex bug. I maintain Pex so I like to rule in or out whether a conversation means I have work to do on a fix.
This is a generic Python question that really has nothing to do with Pex or Pants in particular. Just making that clear.
g
Yeah bro my bad
Yeah I figured there could be some functionality in
pants run
which will figure out that the function is an async function, perhaps similar to how my
pants test
seems to just run async functions with pytest
wasn't trying to infer anything specifically about what pants/pex should/shouldn't do when trying to execute python code
b
FYI, for
pants test
, that'll be pytest doing magic, potentially via a plugin like
pytest-asyncio
. Pants is effectively just running
pytest path/to/file.py
(plus extra args)
1
g
I get that, but what i'm saying is that you type
pants test
and I happen to know its pytest doing it. what's to say when you go pants run there isn't something behind it? I think its a reasonable assumption
f
It's not unreasonable, but that's not the way it works. It's best to think about these things in terms of "entry points" which are how Python starts executing your code. When you run
python -m path.to.module
, Python just synchronously runs
path.to.module
with its name set to
__main__
... the
if __name__ == "__main__": ...
convention is used to detect this case so you can distinguish between when your module is used as an entry point and when its just being loaded for use by another module. Many tools in the Python ecosystem take this further by using an entry point syntax,
path.to.module:callable_object
which will essentially create a small wrapper script around your code that will invoke that callable object on startup. This is necessarily synchronous, because Python is synchronous by default. You would always have to setup and launch your async loop in that entry point function, with e.g.
asyncio.run(async_main())
or something. In the case of pytest, it's Pytest that has its own (synchronous) entry point that invokes your async tests. There isn't really any magic there, it's just a framework for calling your code. You could do this with
pants run
if your pex_binary pointed at some framework that new how to run an async function, for example how a framework like
uvicorn
can run an ASGI app. But this is the point here. There's no async magic in Pants or Pex. It's the underlying frameworks that know how to launch async functions from (default) sync Python code.
❤️ 1