Closed jorenham closed 1 month ago
@KotlinIsland Any thoughts on this?
what's the use case of calling main when it's not main?
so it will return a callable object that might already contain a result?
i would think that it should expect an int to be returned, and call sys.exit(value)
with it. but maybe that's quite simple to do manually
can we invent a new type system concept to represent the semantics of __main__
? would that help at all?
Yea so currently with
import time
import mainpy
@mainpy.main
def app() -> int:
return time.perf_counter_ns()
you'll have app: int
if app.__module__ == '__main__'
, and app: () -> float
otherwise.
Apart from that being awkward to type, it's also something like a convention that a function decorator returns a callable, which in this case, isn't (always) the case.
what's the use case of calling main when it's not main?
Perhaps letting typer
or something do it for you, e.g. in case you have @main def app(): ...
in __init__.py
and also a __main__.py
.
Or in case of sub-packages, where you could python -m zoo.sloth --tickle
but also python -m zoo sloth --tickle
(I've built something similar with typer
once , but the sloth sued me and now I have a restraining order).
so it will return a callable object that might already contain a result?
Yea that's basically my proposal.
Although I doubt that there's a situation where anyone would (or even could) look at the result though 🤷🏻. So I suppose it's just ~an over-engineered~ a fancy solution to an awkward typing situation, as opposed to a practical one.
i would think that it should expect an int to be returned, and call sys.exit(value) with it. but maybe that's quite simple to do manually
Yea I considered doing that before, but then got scared by all the OS-dependent stuff about return codes. Plus, I'm not sure if it'll still work with e.g. click
and typer
that way 🤷🏻
can we invent a new type system concept to represent the semantics of
__main__
?
How about optype.HasModule[Literal['__main__']]
? https://github.com/jorenham/optype/blob/1228112deef5dbbd590b1207173f5d7cd41e9159/optype/_has.py#L81-L83
would that help at all?
🤷🏻♂️
Currently, the
@main
control flow is roughly as follows:__name__ == '__main__'
, evaluate the wrapped function, and return the resultI.E. the
mainpy.main
return type depends on the (untype-able) context. This is rather horrible for type-checkers, because they can't infer what the concrete return type.So I propose the following solution:
Always return a
MainFunction[RT]
instance (suggestions for a better name are welcome) frommainpy.main
. It's a lightweight callable wrapper around the__wrapped__
function patched with functools.update_wrapper` of course are we taking notes, Click?, with several additional attributes:__wrapped__
, the original function (forclick.command
this is the unwrapped/inner function)__module__
, alias for__wrapped__.__module__
result: MainResult[RT] | None
, set if__name__ == '__main__'
, otherwiseNone
options: MainOptions
The
MainResult[RT]
type is aNamedTuple
with the following attributes:value: RT
is_async: bool
(perhaps more attributes could be added later, e.g. a copy of
sys.argv
, or aruntime: timdelta
or something)The
MainOptions
is aTypedDict
representing themainpy.main
keyword-only parameters.I don't see any need for explicit exception handling, since any raised exception should just propagate.